Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sHgS6-00A4mb-09 for pgsql-hackers@arkaria.postgresql.org; Thu, 13 Jun 2024 09:09:02 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1sHgS3-00G39Q-5N for pgsql-hackers@arkaria.postgresql.org; Thu, 13 Jun 2024 09:09:00 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sHgS1-00G38S-8r for pgsql-hackers@lists.postgresql.org; Thu, 13 Jun 2024 09:08:59 +0000 Received: from fhigh2-smtp.messagingengine.com ([103.168.172.153]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1sHgRu-00161u-JO for pgsql-hackers@postgresql.org; Thu, 13 Jun 2024 09:08:56 +0000 Received: from compute6.internal (compute6.nyi.internal [10.202.2.47]) by mailfhigh.nyi.internal (Postfix) with ESMTP id 16F0B1140207; Thu, 13 Jun 2024 05:08:49 -0400 (EDT) Received: from imap48 ([10.202.2.98]) by compute6.internal (MEProxy); Thu, 13 Jun 2024 05:08:49 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=compiler.org; h= cc:content-transfer-encoding:content-type:content-type:date:date :from:from:in-reply-to:in-reply-to:message-id:mime-version :references:reply-to:subject:subject:to:to; s=fm1; t=1718269729; x=1718356129; bh=kFonB4htexszegnhb0zI2A/Uj995gGtZDrEAlMup7Ws=; b= r7mY2StuHE8ui5mL6RBR1jXljq65AV6eZu6e74eD6Hj1r+H0kz6I260h0GFwQJWA sA8h8HarutQ75Q4oJ0flvDMFlx/x+tFfOpICnBTZjTp8IsB90vnwCwvr9o3TpImB 5EoFD3fGPojFkhL+9vXrt7vtDF1G6gqhLQZBRhh8axuTi3b+bUNZocmWXkR+g88T FDiyLxYFHOAmhxLCXC0VJywOkjRO2ubWzJWoyb7y8YTignMNGrBaAn6xSyVdUor7 vxfo9SRvH5ZQg/mCmvAYpkdFcfwj4QGA3hu6viSrZq+y/YKrq8ptZ2jZmiMCwSJP YcnUVYX2lLCVP9CQIaighA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-transfer-encoding:content-type :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-proxy :x-me-sender:x-me-sender:x-sasl-enc; s=fm1; t=1718269729; x= 1718356129; bh=kFonB4htexszegnhb0zI2A/Uj995gGtZDrEAlMup7Ws=; b=D TkMWNj4UlKysppMpyr6h6rPbrUeSI8vGRl61j1RF8cy/CTLXW4vQJxSClmBzgOih Q+6zzz3brEaRhlwiGa1QDH9fxp9iXxS1b4L7gClGZIT3sLq1109t9IqT7F5Bn87R jWRlnnVcI+FPCMzjfNuQkFa1iXf45vlBfZZCc3hs572JCcw/NFQSnO7nIM7StD4o 7MtItfARgqn9t6N2+5oHk24GOqVlpo1ZNLNNCY43PFmAdd7L/XWtRGsvil5AfKAe ESMsiOPMSMRniUQqtR0vsLUInnWqYbaLeuk3DudxiGCKEEVpLSo4d9rfHX26BNjw j8np+dzRcuXarzsIFQlDA== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvledrfedujedguddtucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmne cujfgurhepofgfggfkjghffffhvffutgfgsehtqhertderreejnecuhfhrohhmpedflfho vghlucflrggtohgsshhonhdfuceojhhovghlsegtohhmphhilhgvrhdrohhrgheqnecugg ftrfgrthhtvghrnhepfeeukeegvdeuvdeikeeffeegleekudfgfffgveefgfehjeejkedv feeileegveeunecuffhomhgrihhnpehgihhthhhusgdrtghomhdpmhhulhhtihhplhhitg grthhiohhnrdhrshenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhl fhhrohhmpehjohgvlhestghomhhpihhlvghrrdhorhhg X-ME-Proxy: Feedback-ID: ic6394509:Fastmail Received: by mailuser.nyi.internal (Postfix, from userid 501) id BBBEE31A0065; Thu, 13 Jun 2024 05:08:48 -0400 (EDT) X-Mailer: MessagingEngine.com Webmail Interface User-Agent: Cyrus-JMAP/3.11.0-alpha0-515-g87b2bad5a-fm-20240604.001-g87b2bad5 MIME-Version: 1.0 Message-Id: In-Reply-To: References: <7f95163f-2019-4416-a042-6e2141619e5d@app.fastmail.com> Date: Thu, 13 Jun 2024 11:08:25 +0200 From: "Joel Jacobson" To: "Aaron Altman" , pgsql-hackers Subject: Re: Optimize numeric.c mul_var() using the Karatsuba algorithm Content-Type: text/plain;charset=utf-8 Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On Tue, Jun 11, 2024, at 19:16, Aaron Altman wrote: > Hi Joel, thanks for posting this.=C2=A0 Although I have only a cursory=20 > familiarity with fast multiplication algorithms, I'd like to try and=20 > give it a review.=C2=A0 To start with, can you help me understand the = choice=20 > of this algorithm versus others like Toom?=C2=A0 If this looks correct= on a=20 > closer view I'll propose it for inclusion. Along the way though I'd li= ke=20 > to have it explicitly called out whether this is superior in general t= o=20 > other choices, better for more realistic use cases, simpler, clearer t= o=20 > license or something similar.=C2=A0 It would be nice for future dicuss= ions to=20 > have some context around whether it would make sense to have condition= s=20 > to choose other algorithms as well, or if this one is generally the be= st=20 > for what Postgres users are usually doing. > > Continuing with code review in any case.=C2=A0 Interested to hear more. Hi Aaron, thanks for looking at this! The choice of best algorithm depends on the factor sizes. The larger factor sizes, the more complicated algorithm can be "afforded= ". List of fast multiplication algorithms, ordered by factor sizes they are suitable for: - Long multiplication, aka "schoolbook" multiplication. - Karatsuba - Toom-3 - Sch=C3=B6nhage=E2=80=93Strassen algorithm (Fast Fourier Transform) The Toom-3 algorithm can be modified to split the smaller and larger fac= tors into different number of parts. The notation used at Wikipedia is e.g. T= oom-2.5 which I think means splitting the larger into three parts, and the small= er into two parts, while GMP uses Toom32 to mean the same thing. Personally, I think GMPs notation is easier to understand as the number = of parts can be directly derived from the name. I experimented with implementing Toom-3 as well, but there was only a ma= rginal win, at very large factor sizes, and since numeric's max ndigits (number of base-digits) is capped at 32768, I didn't think it was worth = it, since it adds quite a lot of complexity. The Karatsuba algorithm is the next step in the hierarchy of fast multip= lication algorithms, and all other bigint libs I've looked at implement Karatsuba, even if they also implement Toom-3, since Karatsuba is faster than Toom-= 3 for sufficiently small factors, but that are at the same time sufficiently l= arge for Karatsuba to be faster than schoolbook. I was initially surprised by the quite large threshold, where Karatsuba = started to be a win over schoolbook. I think the explanation why mul_var() stays fast up to quite remarkably = high factor sizes, could be a combination of several things, such as: - mul_var() is already heavily optimized, with clever tricks, such as deferred carry propagation. - numeric uses NBASE=3D10000, while other bigint libs usually use a powe= r of two. In the Karatsuba implementation, I tried to keep the KARATSUBA_CONDITION= () quite simple, but it's way more complex than what most bigint libs use, that usually just check if the smaller factor is smaller than some thres= hold, and if so, use schoolbook. For instance, this is what Rust's num-bigint = does: if x.len() <=3D 32 { // Long multiplication } else if x.len() * 2 <=3D y.len() { // Half-Karatsuba, for factors with significant length disparity } else if x.len() <=3D 256 { // Karatsuba multiplication } else { // Toom-3 multiplication } Source: https://github.com/rust-num/num-bigint/blob/master/src/biguint/m= ultiplication.rs#L101 Side note: When working on Karatsuba in mul_var(), I looked at some othe= r bigint implementations, to try to understand their threshold functions. I noticed that Rust's num-bigint didn't optimise for factors with signif= icant length disparity, so I contributed a patch based on my "Half-Karatsuba" = idea, that I got when working with mul_var(), which has now been merged: https://github.com/rust-num/num-bigint/commit/06b61c8138ad8a9959ac54d977= 3d0a9ebe25b346 In mul_var(), if we don't like the complexity of KARATSUBA_CONDITION(), we could go for a more traditional threshold approach, i.e. just checking the smaller factor size. However, I believe that would be at the expense of missing out of some performance gains. I've tried quite hard to find the best KARATSUBA_CONDITION(), but I foun= d it to be a really hard problem, the differences between different CPU architec= tures, in combination with wanting a simple expression, means there is no obvio= us perfect threshold function, there will always be a trade-off. I eventually stopped trying to improve it, and just settled on the versi= on in the patch, and thought that I'll leave it up to the community to give fe= edback on what complexity for the threshold function is motivated. If we absolu= tely just want to check the smallest factor size, like Rust, then it's super = simple, then the threshold can easily be found just by testing different values. It's when both factor sizes are input to the threshold function that mak= es it complicated. /Joel