You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Suppose we are unioning together $n$ many objects of $k$ keys each. topdown.builtinObjectUnionN() will make $n$ many calls to topdown.mergeWithOverwrite(), implying $\Omega(1)$ calls to ast.object.MergeWith(). Though MergeWith appears linear at first glance, the calls are not independent from one another. The first call is merging two objects of $k$ keys each, the second call merges an object of $2k$ keys with an object of $k$ keys, and so on. Thus the actual work done by MergeWith in the inner loop ends up being: $O\left(2k + 3k + 4k + 5k + \dots\right) = O\left(k \left(\frac12 n (n+1) -1\right)\right) \approx O(kn^2)$.
This occurs in the optimal case where $\Omega(1) = 1$. If the objects contain complex nested objects as values rather than primitive types, then the actual running time could be substantially worse.
$ uname -a
Darwin styra-macbook.local 21.5.0 Darwin Kernel Version 21.5.0: Tue Apr 26 21:08:37 PDT 2022; root:xnu-8020.121.3~4/RELEASE_ARM64_T6000 arm64
$ system_profiler SPSoftwareDataType SPHardwareDataType
Software:
System Software Overview:
System Version: macOS 12.4 (21F79)
Kernel Version: Darwin 21.5.0
Boot Volume: Macintosh HD
Boot Mode: Normal
Computer Name: styra-macbook
User Name: Charles Daniels (cad)
Secure Virtual Memory: Enabled
System Integrity Protection: Enabled
Time since boot: 5 days 21:41
Hardware:
Hardware Overview:
Model Name: MacBook Pro
Model Identifier: MacBookPro18,3
Chip: Apple M1 Pro
Total Number of Cores: 8 (6 performance and 2 efficiency)
Memory: 32 GB
System Firmware Version: 7459.121.3
OS Loader Version: 7459.121.3
Serial Number (system): REDACTED
Hardware UUID: REDACTED
Provisioning UDID: REDACTED
Activation Lock Status: Disabled
$ opa version
Version: 0.43.0
Build Commit: d75bbdd
Build Timestamp: 2022-07-29T21:15:42Z
Build Hostname: Mac-1659129125033.local
Go Version: go1.18.4
Platform: darwin/arm64
WebAssembly: unavailable
Note that unlike in #4979, this is not an apples-to-apples comparison, because object.union_n() is not expressible in the general case (to my knowledge) using pure Rego. The fact that object.union_n() is slower than the given Rego code is not an "obvious" problem in of itself, it's the fact that the it grows far faster than linearly on the number of objects to merge.
Although the asymptotic analysis is sufficient on it's own to demonstrate this, I have implemented the following ad-hoc benchmark to demonstrate. Note that this needs a version of Bash new enough to support EPOCHREALTIME. Notice that though nsets varies with each run of the benchmark, size is fixed at 250 for all of them. This in the previous asymptotic analysis, $n$ is allowed to vary, and $k$ is fixed at 250.
This commit fixes a performance regression for the object.union_n builtin,
discovered in issue open-policy-agent#4985.
The original logic for the builtin did pairwise mergeWithOverwrite calls
between the input Objects, resulting in many wasted intermediate result
Objects that were almost immediately discarded. The updated builtin uses
a new algorithm to do a single pass across the input Objects, respecting
the "last assignment wins, with merges" semantics of the original builtin
implementation.
In the included benchmarks, this provides a 2x speed and 2-3x memory
efficiency improvement over using a pure-Rego comprehension to do the
same job, and a 6x or greater improvement over the original implementation
on all metrics as input Object arrays grow larger.
Fixesopen-policy-agent#4985
Signed-off-by: Philip Conrad <philipaconrad@gmail.com>
#5127)
This commit fixes a performance regression for the object.union_n builtin,
discovered in issue #4985.
The original logic for the builtin did pairwise mergeWithOverwrite calls
between the input Objects, resulting in many wasted intermediate result
Objects that were almost immediately discarded. The updated builtin uses
a new algorithm to do a single pass across the input Objects, respecting
the "last assignment wins, with merges" semantics of the original builtin
implementation.
In the included benchmarks, this provides a 2x speed and 2-3x memory
efficiency improvement over using a pure-Rego comprehension to do the
same job, and a 6x or greater improvement over the original implementation
on all metrics as input Object arrays grow larger.
Fixes#4985
Signed-off-by: Philip Conrad <philipaconrad@gmail.com>
This is conceptually the same problem from #4979.
Suppose we are unioning together$n$ many objects of $k$ keys each. $n$ many calls to $\Omega(1)$ calls to $k$ keys each, the second call merges an object of $2k$ keys with an object of $k$ keys, and so on. Thus the actual work done by $O\left(2k + 3k + 4k + 5k + \dots\right) = O\left(k \left(\frac12 n (n+1) -1\right)\right) \approx O(kn^2)$ .
topdown.builtinObjectUnionN()
will maketopdown.mergeWithOverwrite()
, implyingast.object.MergeWith()
. ThoughMergeWith
appears linear at first glance, the calls are not independent from one another. The first call is merging two objects ofMergeWith
in the inner loop ends up being:This occurs in the optimal case where$\Omega(1) = 1$ . If the objects contain complex nested objects as values rather than primitive types, then the actual running time could be substantially worse.
Empirical Benchmarks
With the
object.union_n()
builtin:With pure Rego:
System info:
Note that unlike in #4979, this is not an apples-to-apples comparison, because
object.union_n()
is not expressible in the general case (to my knowledge) using pure Rego. The fact thatobject.union_n()
is slower than the given Rego code is not an "obvious" problem in of itself, it's the fact that the it grows far faster than linearly on the number of objects to merge.Although the asymptotic analysis is sufficient on it's own to demonstrate this, I have implemented the following ad-hoc benchmark to demonstrate. Note that this needs a version of Bash new enough to support$n$ is allowed to vary, and $k$ is fixed at 250.
EPOCHREALTIME
. Notice that thoughnsets
varies with each run of the benchmark,size
is fixed at 250 for all of them. This in the previous asymptotic analysis,As we can see, between n=100 and n=500 (factor of 5x increase in input size), the running time has gone from 0.49s to 12.68s, a 25x increase.
The text was updated successfully, but these errors were encountered: