Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1pQwVn-0006AO-0J for pgsql-hackers@arkaria.postgresql.org; Sat, 11 Feb 2023 20:30:19 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1pQwVl-0000th-D5 for pgsql-hackers@arkaria.postgresql.org; Sat, 11 Feb 2023 20:30:17 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1pQwVj-0000lJ-Kq for pgsql-hackers@lists.postgresql.org; Sat, 11 Feb 2023 20:30:17 +0000 Received: from wout2-smtp.messagingengine.com ([64.147.123.25]) by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1pQwVb-0003Y1-4A for pgsql-hackers@lists.postgresql.org; Sat, 11 Feb 2023 20:30:15 +0000 Received: from compute2.internal (compute2.nyi.internal [10.202.2.46]) by mailout.west.internal (Postfix) with ESMTP id 44F76320092C; Sat, 11 Feb 2023 15:30:02 -0500 (EST) Received: from mailfrontend1 ([10.202.2.162]) by compute2.internal (MEProxy); Sat, 11 Feb 2023 15:30:02 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=anarazel.de; h= cc:cc:content-type:date:date:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:sender:subject :subject:to:to; s=fm1; t=1676147401; x=1676233801; bh=z8rfdAYnhd kWh0SY2MXUmWHc67doOkUedD9BI/wcYJg=; b=OV1rIgpw/B3j3V8fBt3jsZuIKO F7KiYJ38QUBmX2lXhvRN/aK8aSeUDyWhWRHQF9YZl/Qw+WeGCmvY7Wxc6o9k5fiO WpmAScxNtTiwzZv6c9sW8LChxkkQrULajisJVfsbC6mB3wTezMGeKjNBhl+V17Zx h6+2EWuXkfg6ZZGN/TiXF/hA82TVxBF9B1BG/NoIkq906UPrlznur1chxOFhqAFt BB0Jeg387OIKsYG3jSbJdYbrUAMXlq4LlzmYYfRFkxTTpGiUlpDMHTevS+dxLNh1 7eudD4e71DLaziQsuhLTL6DoUQww6UAXOc2q/ssLGUg76nmmfWaGki3xwHqQ== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-type:date:date:feedback-id :feedback-id:from:from:in-reply-to:in-reply-to:message-id :mime-version:references:reply-to:sender:subject:subject:to:to :x-me-proxy:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm1; t=1676147401; x=1676233801; bh=z8rfdAYnhdkWh0SY2MXUmWHc67do OkUedD9BI/wcYJg=; b=ZZnu/halybKyG17Lps0Z3+g83H8H8caw2AoO1tavG+6m K5GC0LNtXOABCVTU84o2XQpn7BFJZDkiV4SdK0SM9wLyB4MXqrG6f6F5H4neSzWQ osgWWpevP69YDjTTjIqFmMQEXDSgE+PBnLz0yiSkY+f7tJPCuAtHE1BkOBbLXHtE QhKNSEBmlutipv0RpXZZKyt1ThDNQPnxwKHyIfsEbAJvkJpM6dsStxbP6LynRCEd OOOqjRo67JB+abFg7/9n2YYVzWG3d3if8Y2YQZUyisB57z6+4YDMMWGN2l9BG0JC Ub5jIhCCAKFfcp/77OBouy7oGtOVbawes7S6X3tw1g== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvhedrudehjedgudefjecutefuodetggdotefrod ftvfcurfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfgh necuuegrihhlohhuthemuceftddtnecunecujfgurhepfffhvfevuffkfhggtggujgesth dtredttddtvdenucfhrhhomheptehnughrvghsucfhrhgvuhhnugcuoegrnhgurhgvshes rghnrghrrgiivghlrdguvgeqnecuggftrfgrthhtvghrnhepvdfffeevhfetveffgeeite efhfdtvdffjeevhfeuteegleduheetveduieettddunecuvehluhhsthgvrhfuihiivgep tdenucfrrghrrghmpehmrghilhhfrhhomheprghnughrvghssegrnhgrrhgriigvlhdrug gv X-ME-Proxy: Feedback-ID: id4a34324:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Sat, 11 Feb 2023 15:30:01 -0500 (EST) Date: Sat, 11 Feb 2023 12:29:59 -0800 From: Andres Freund To: Ankit Kumar Pandey Cc: pghackers , David Rowley Subject: Re: Sort optimizations: Making in-memory sort cache-aware Message-ID: <20230211202959.lkhrxiy7awsyctef@awork3.anarazel.de> References: <444ccfcc-06d6-0160-f662-9fa2075229ad@gmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <444ccfcc-06d6-0160-f662-9fa2075229ad@gmail.com> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi, On 2023-02-11 17:49:02 +0530, Ankit Kumar Pandey wrote: > 2. Frequent cache misses > > Issue #1 is being looked in separate patch. I am currently looking at #2. > > Possible solution was to batch tuples into groups (which can fit into L3 > cache) before pushing them to sort function. > > After looking at different papers on this (multi-Quicksort, memory-tuned > quicksort, Samplesort and various distributed sorts), although they look > promising (especially samplesort), I would like to get more inputs as > changes look bit too steep and may or may not be in of scope of solving > actual problem in hand. > > Please let me know your opinions, do we really need to re-look at quicksort > for this use-case or we can perform optimization without major change in > core sorting algorithm? Are we are open for trying new algorithms for sort? I think it'll require some experimentation to know what we can and should do. Clearly we're not going to do anything fundamental if the gains are a few percent. But it's not hard to imagine that the gains will be substantially larger. I believe that a significant part of the reason we have low cache hit ratios once the input gets larger, is that we kind of break the fundamental benefit of qsort: The reason quicksort is a good sorting algorithm, despite plenty downsides, is that it has pretty decent locality, due to its divide and conquer approach. However, tuplesort.c completely breaks that for > 1 column sorts. While spatial locality for accesses to the ->memtuples array is decent during sorting, due to qsort's subdividing of the problem, the locality for access to the tuples is *awful*. The memtuples array is reordered while sorting, but the tuples themselves aren't. Unless the input data is vaguely presorted, the access pattern for the tuples has practically zero locality. The only reason that doesn't completely kill us is that SortTuple contains datum1 inline and that abbreviated keys reduce the cost of by-reference datums in the first column. There are things we could do to improve upon this that don't require swapping out our sorting implementation wholesale. One idea is to keep track of the distinctness of the first column sorted and to behave differently if it's significantly lower than the number of to be sorted tuples. E.g. by doing a first sort solely on the first column, then reorder the MinimalTuples in memory, and then continue normally. There's two main problems with that idea: 1) It's hard to re-order the tuples in memory, without needing substantial amounts of additional memory 2) If the second column also is not very distinct, it doesn't buy you much, if anything. But it might provide sufficient benefits regardless. And a naive version, requiring additional memory, should be quick to hack up. I have *not* looked at a whole lot of papers of cache optimized sorts, and the little I did was not recently. Partially because I am not sure that they are that applicable to our scenarios: Most sorting papers don't discuss variable-width data, nor a substantial amount of cache-polluting work while gathering the data that needs to be sorted. I think: > Possible solution was to batch tuples into groups (which can fit into L3 > cache) before pushing them to sort function. is the most general solution to the issue outlined above. I wouldn't try to implement this via a full new sorting algorithm though. My suggestion would be to collect a roughly ~L3 sized amount of tuples, sort just those using the existing code, allocate new memory for all the corresponding MinimalTuples in one allocation, and copy the MinimalTuples into that, obviously in ->memtuples order. Even if we just use the existing code for the overall sort after that, I'd expect that to yield noticable benefits. It's very likely we can do better than just doing a plain sort of everything after that. You effectively end up with a bounded number of pre-sorted blocks, so the most obvious thing to try is to build a heap of those blocks and effectively do a heapsort between the presorted blocks. A related, but separate, improvement is to reduce / remove the memory allocation overhead. The switch to GenerationContext helped some, but still leaves a bunch of overhead. And it's not used for bounded sorts right now. We don't palloc/pfree individual tuples during a normal sorts, but we do have some, for bounded sorts. I think with a reasonable amount of work we could avoid that for all tuples in ->tuplecontext. And switch to a trivial bump allocator, getting rid of all allocator overhead. The biggest source of individual pfree()s in the bounded case is that we unconditionally copy the tuple into base->tuplecontext during puttuple. Even though quite likely we'll immediately free it in the "case TSS_BOUNDED" block. We could instead pre-check that the tuple won't immediately be discarded, before copying it into tuplecontext. Only in the TSS_BOUNDED, case, of course. I think we also can replace the individual freeing of tuples in tuplesort_heap_replace_top(), by allowing a number of dead tuples to accumulate (up to work_mem/2 maybe), and then copying the still living tuples into new memory context, freeing the old one. While that doesn't sound cheap, in bounded sorts the number of tuples commonly is quite limited, the pre-check before copying the tuple will prevent this from occurring too often, the copy will result in higher locality, and, most importantly, the reduced palloc() overhead (~25% or so with aset.c) will result in a considerably higher cache hit ratio / lower memory usage. Greetings, Andres Freund