postgresql/src/backend/lib/knapsack.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

112 lines
3.1 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* knapsack.c
* Knapsack problem solver
*
* Given input vectors of integral item weights (must be >= 0) and values
* (double >= 0), compute the set of items which produces the greatest total
* value without exceeding a specified total weight; each item is included at
* most once (this is the 0/1 knapsack problem). Weight 0 items will always be
* included.
*
* The performance of this algorithm is pseudo-polynomial, O(nW) where W is the
* weight limit. To use with non-integral weights or approximate solutions,
* the caller should pre-scale the input weights to a suitable range. This
* allows approximate solutions in polynomial time (the general case of the
* exact problem is NP-hard).
*
* Copyright (c) 2017-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/backend/lib/knapsack.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <math.h>
#include <limits.h>
#include "lib/knapsack.h"
#include "miscadmin.h"
#include "nodes/bitmapset.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
/*
* DiscreteKnapsack
*
* The item_values input is optional; if omitted, all the items are assumed to
* have value 1.
*
* Returns a Bitmapset of the 0..(n-1) indexes of the items chosen for
* inclusion in the solution.
*
* This uses the usual dynamic-programming algorithm, adapted to reuse the
* memory on each pass (by working from larger weights to smaller). At the
* start of pass number i, the values[w] array contains the largest value
* computed with total weight <= w, using only items with indices < i; and
* sets[w] contains the bitmap of items actually used for that value. (The
* bitmapsets are all pre-initialized with an unused high bit so that memory
* allocation is done only once.)
*/
Bitmapset *
DiscreteKnapsack(int max_weight, int num_items,
int *item_weights, double *item_values)
{
MemoryContext local_ctx = AllocSetContextCreate(CurrentMemoryContext,
"Knapsack",
Rethink MemoryContext creation to improve performance. This patch makes a number of interrelated changes to reduce the overhead involved in creating/deleting memory contexts. The key ideas are: * Include the AllocSetContext header of an aset.c context in its first malloc request, rather than allocating it separately in TopMemoryContext. This means that we now always create an initial or "keeper" block in an aset, even if it never receives any allocation requests. * Create freelists in which we can save and recycle recently-destroyed asets (this idea is due to Robert Haas). * In the common case where the name of a context is a constant string, just store a pointer to it in the context header, rather than copying the string. The first change eliminates a palloc/pfree cycle per context, and also avoids bloat in TopMemoryContext, at the price that creating a context now involves a malloc/free cycle even if the context never receives any allocations. That would be a loser for some common usage patterns, but recycling short-lived contexts via the freelist eliminates that pain. Avoiding copying constant strings not only saves strlen() and strcpy() overhead, but is an essential part of the freelist optimization because it makes the context header size constant. Currently we make no attempt to use the freelist for contexts with non-constant names. (Perhaps someday we'll need to think harder about that, but in current usage, most contexts with custom names are long-lived anyway.) The freelist management in this initial commit is pretty simplistic, and we might want to refine it later --- but in common workloads that will never matter because the freelists will never get full anyway. To create a context with a non-constant name, one is now required to call AllocSetContextCreateExtended and specify the MEMCONTEXT_COPY_NAME option. AllocSetContextCreate becomes a wrapper macro, and it includes a test that will complain about non-string-literal context name parameters on gcc and similar compilers. An unfortunate side effect of making AllocSetContextCreate a macro is that one is now *required* to use the size parameter abstraction macros (ALLOCSET_DEFAULT_SIZES and friends) with it; the pre-9.6 habit of writing out individual size parameters no longer works unless you switch to AllocSetContextCreateExtended. Internally to the memory-context-related modules, the context creation APIs are simplified, removing the rather baroque original design whereby a context-type module called mcxt.c which then called back into the context-type module. That saved a bit of code duplication, but not much, and it prevented context-type modules from exercising control over the allocation of context headers. In passing, I converted the test-and-elog validation of aset size parameters into Asserts to save a few more cycles. The original thought was that callers might compute size parameters on the fly, but in practice nobody does that, so it's useless to expend cycles on checking those numbers in production builds. Also, mark the memory context method-pointer structs "const", just for cleanliness. Discussion: https://postgr.es/m/2264.1512870796@sss.pgh.pa.us
2017-12-13 19:55:12 +01:00
ALLOCSET_SMALL_SIZES);
MemoryContext oldctx = MemoryContextSwitchTo(local_ctx);
double *values;
Bitmapset **sets;
Bitmapset *result;
int i,
j;
Assert(max_weight >= 0);
Assert(num_items > 0 && item_weights);
values = palloc((1 + max_weight) * sizeof(double));
sets = palloc((1 + max_weight) * sizeof(Bitmapset *));
for (i = 0; i <= max_weight; ++i)
{
values[i] = 0;
sets[i] = bms_make_singleton(num_items);
}
for (i = 0; i < num_items; ++i)
{
int iw = item_weights[i];
double iv = item_values ? item_values[i] : 1;
for (j = max_weight; j >= iw; --j)
{
int ow = j - iw;
if (values[j] <= values[ow] + iv)
{
/* copy sets[ow] to sets[j] without realloc */
if (j != ow)
{
sets[j] = bms_del_members(sets[j], sets[j]);
sets[j] = bms_add_members(sets[j], sets[ow]);
}
sets[j] = bms_add_member(sets[j], i);
values[j] = values[ow] + iv;
}
}
}
MemoryContextSwitchTo(oldctx);
result = bms_del_member(bms_copy(sets[max_weight]), num_items);
MemoryContextDelete(local_ctx);
return result;
}