Optimize convolution with Kronecker substitution and performance tuning - #38
Closed
shakayami wants to merge 1 commit into
Closed
Optimize convolution with Kronecker substitution and performance tuning#38shakayami wants to merge 1 commit into
shakayami wants to merge 1 commit into
Conversation
Profiling the existing NTT showed the cost is almost entirely CPython interpreter overhead in the butterfly inner loops, and that the NTT is only the right algorithm once both operands are large. Three changes: 1. Add an integer-packing (Kronecker substitution) path. Each coefficient is placed in its own zero-padded slot of a big integer, so a single CPython multiply performs the whole convolution in C. This is faster than the NTT whenever the smaller operand is small, the operands are lopsided, or the coefficients are small; a fitted cost model picks between the two. Slot width is derived from the actual maximum coefficient rather than the modulus, and struct is used for packing/unpacking instead of per-element to_bytes. 2. Optimize the butterfly loops: hoist instance attributes into locals, precompute the p/2p/3p index offsets, share common subexpressions between the four radix-4 outputs, and skip the twiddle multiplications for the groups where the rotation is 1. 3. Fold the 1/z normalization into b before its transform, so the inverse transform's output needs no extra scaling pass or extra list. The schoolbook path now triggers on n*m instead of min(n,m), since integer packing beats it above roughly 40 coefficient products. Measured on CPython 3.11 with mod 998244353 (old -> new): n=m=1000 7.8ms -> 1.6ms (4.9x) n=m=8192 80.3ms -> 46.8ms (1.7x) n=m=65536 1040.3ms -> 690.0ms (1.5x) n=65536, m=256 773.5ms -> 50.1ms (15.4x) n=262144, m=256 4121.7ms -> 200.6ms (20.6x) n=m=8192, 0/1 76.9ms -> 5.8ms (13.2x) Peak memory is unchanged to 75% lower. Tests cover both dispatch paths, unnormalized/negative/zero inputs and several moduli, and the benchmark suite gains a lopsided convolution workload alongside the balanced one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015N4uDXqbEPEaCbPoE4Utz4
📊 Performance Benchmark Results⏱️ Execution time (mean, lower is better)
💾 Peak memory (lower is better)
Base = target branch, PR = this branch. Negative delta = improvement. ✅ improvement ≥ 1%, |
Open
Owner
Author
|
yosupo judgeだと早くなってない |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR significantly optimizes the convolution implementation by adding a Kronecker substitution path for small-to-medium inputs and applying various performance improvements to the NTT-based path.
Key Changes
Kronecker Substitution Path: Added
_convolution_int()method that uses integer multiplication with zero-padded slots to compute convolutions for inputs where the product coefficients fit within fixed-width integers. This is faster than NTT for many practical cases.Adaptive Dispatch: The
convolution()method now uses a cost model to choose between three paths:Performance Optimizations:
mod,rate2,rate3,imag, etc.) into local variables inbutterfly()andbutterfly_inv()to reduce attribute lookup overhead in innermost loopsrot == 1case in butterfly operations to avoid unnecessary multiplicationsp2,p3,a02,a13, etc.) to reduce redundant calculationsStruct-based Packing: Implemented
_pack()and_unpack()methods using Python'sstructmodule for efficient serialization of coefficient arrays into integers, with caching of struct formatters for repeated use.Comprehensive Testing: Added extensive test coverage including:
Benchmark Workload: Added
convolution_lopsidedbenchmark to measure performance on the common case of multiplying a long array by a short one.Implementation Details
The cost model compares the estimated runtime of Kronecker substitution (based on Karatsuba integer multiplication complexity) against NTT (O(z log z) where z is the next power of 2). The threshold is empirically fitted to CPython 3.11 performance characteristics.
The Kronecker substitution approach encodes each coefficient into a fixed-width slot such that the product of two encoded values can be decoded without carry interference between slots, allowing CPython's optimized integer multiplication to perform the entire convolution in one operation.
https://claude.ai/code/session_015N4uDXqbEPEaCbPoE4Utz4