diff --git a/crypto/modexp/benchmark_comprehensive_test.go b/crypto/modexp/benchmark_comprehensive_test.go index f4a518d33e..e9cd106a6a 100644 --- a/crypto/modexp/benchmark_comprehensive_test.go +++ b/crypto/modexp/benchmark_comprehensive_test.go @@ -6,7 +6,6 @@ import ( "github.com/ethereum/go-ethereum/crypto/modexp/bigint" gmpcwrapper "github.com/ethereum/go-ethereum/crypto/modexp/gmp/cwrapper" gmpgeneric "github.com/ethereum/go-ethereum/crypto/modexp/gmp/generic" - gmppool "github.com/ethereum/go-ethereum/crypto/modexp/gmp/pool" ) // generateWorstCase generates a byte array with all bits set to 1 @@ -47,7 +46,6 @@ func BenchmarkComprehensive(b *testing.B) { {"BigInt", bigint.ModExp}, {"GMPGeneric", gmpgeneric.ModExp}, {"GMPCWrapper", gmpcwrapper.ModExp}, - {"GMPPooled", gmppool.ModExp}, } for _, tc := range testCases { @@ -100,7 +98,6 @@ func BenchmarkWorstCaseOnly(b *testing.B) { {"BigInt", bigint.ModExp}, {"GMPGeneric", gmpgeneric.ModExp}, {"GMPCWrapper", gmpcwrapper.ModExp}, - {"GMPPooled", gmppool.ModExp}, } b.Run("AllFF", func(b *testing.B) { @@ -147,7 +144,6 @@ func BenchmarkMemoryProfile(b *testing.B) { {"BigInt", bigint.ModExp}, {"GMPGeneric", gmpgeneric.ModExp}, {"GMPCWrapper", gmpcwrapper.ModExp}, - {"GMPPooled", gmppool.ModExp}, } for _, size := range sizes { @@ -230,12 +226,6 @@ func BenchmarkComparison(b *testing.B) { _, _ = gmpcwrapper.ModExp(data.base, data.exp, data.mod) } }) - - b.Run("GMPPooled", func(b *testing.B) { - for i := 0; i < b.N; i++ { - _, _ = gmppool.ModExp(data.base, data.exp, data.mod) - } - }) }) } } \ No newline at end of file diff --git a/crypto/modexp/gmp/README.md b/crypto/modexp/gmp/README.md index 5168e9f55c..e2264ffff7 100644 --- a/crypto/modexp/gmp/README.md +++ b/crypto/modexp/gmp/README.md @@ -17,7 +17,7 @@ You need to have GMP development libraries installed on your system: This package provides a GMP-backed implementation for modular exponentiation. The API can be expanded, however right now, the main usage is for the modexp precompile. -There are currently two APIs. This is because its unclear if a direct API is better or using a syncPool with a more flexible API is better. +There are currently two implementations: generic (using Go wrapper types) and cwrapper (direct C calls). ### Byte Array Interface (Recommended) @@ -55,18 +55,6 @@ result.ExpMod(base, exp, mod) fmt.Printf("Result: %s\n", result) ``` -### Pooled Interface (High Performance) - -```go -// Create a pool once -pool := gmp.NewIntPool() - -// Use it for many operations -for i := 0; i < 1000000; i++ { - result := gmp.ExpModPooled(pool, base, exp, mod) - // Process result... -} -``` ## Testing diff --git a/crypto/modexp/gmp/pool/gmp.go b/crypto/modexp/gmp/pool/gmp.go deleted file mode 100644 index fe103f073a..0000000000 --- a/crypto/modexp/gmp/pool/gmp.go +++ /dev/null @@ -1,96 +0,0 @@ -package gmp - -// #include -import "C" -import ( - "sync" - generic "github.com/ethereum/go-ethereum/crypto/modexp/gmp/generic" -) - -// IntPool provides a pool of reusable GMP Int objects to reduce allocations -type IntPool struct { - pool sync.Pool -} - -// NewIntPool creates a new pool for GMP Int objects -func NewIntPool() *IntPool { - return &IntPool{ - pool: sync.Pool{ - New: func() interface{} { - return generic.NewInt() - }, - }, - } -} - -// Get retrieves an Int from the pool -func (p *IntPool) Get() *generic.Int { - return p.pool.Get().(*generic.Int) -} - -// Put returns an Int to the pool after clearing it -func (p *IntPool) Put(i *generic.Int) { - // Clear the Int to avoid keeping large numbers in memory - i.SetUint64(0) - p.pool.Put(i) -} - -// ExpModPooled performs modular exponentiation using pooled Int objects -// This is useful for high-throughput scenarios where you want to minimize allocations -// This function matches the behavior of the EVM modexp precompile -func ExpModPooled(pool *IntPool, base, exp, mod []byte) []byte { - // Handle empty modulus - return empty result (EVM behavior) - if len(mod) == 0 { - return []byte{} - } - - // Get Ints from pool - baseInt := pool.Get() - expInt := pool.Get() - modInt := pool.Get() - resultInt := pool.Get() - - // Ensure we return Ints to pool when done - defer func() { - pool.Put(baseInt) - pool.Put(expInt) - pool.Put(modInt) - pool.Put(resultInt) - }() - - // Set values - baseInt.SetBytes(base) - expInt.SetBytes(exp) - modInt.SetBytes(mod) - - // Check for zero modulus - if modInt.BitLen() == 0 { - return []byte{} - } - - // Special case: base has bit length 1 (base == 1) - if baseInt.BitLen() == 1 { - // Just return base % mod - resultInt.Mod(baseInt, modInt) - } else { - // Normal case: perform modular exponentiation - resultInt.ExpMod(baseInt, expInt, modInt) - } - - // Get result bytes (this allocates, but much less than creating new Ints) - return resultInt.Bytes() -} - -// ModExp performs modular exponentiation using a new pool for each operation -// For better performance, create a pool once and reuse it with ExpModPooled -func ModExp(base, exp, mod []byte) ([]byte, error) { - pool := NewIntPool() - result := ExpModPooled(pool, base, exp, mod) - return result, nil -} - -// PreallocatedExpMod performs modular exponentiation with pre-allocated Int objects -// This gives the caller full control over object lifecycle -func PreallocatedExpMod(result, base, exp, mod *generic.Int) { - result.ExpMod(base, exp, mod) -} \ No newline at end of file diff --git a/crypto/modexp/gmp/pool/gmp_test.go b/crypto/modexp/gmp/pool/gmp_test.go deleted file mode 100644 index b7bb27ddf0..0000000000 --- a/crypto/modexp/gmp/pool/gmp_test.go +++ /dev/null @@ -1,236 +0,0 @@ -package gmp - -import ( - "math/big" - "sync" - "testing" -) - -// TestIntPool tests basic pool functionality -func TestIntPool(t *testing.T) { - pool := NewIntPool() - - // Get an Int from pool - i1 := pool.Get() - i1.SetString("12345", 10) - - // Return it to pool - pool.Put(i1) - - // Get another Int (should be the same one, cleared) - i2 := pool.Get() - if i2.String() != "0" { - t.Errorf("Expected cleared Int, got %s", i2.String()) - } - - // Verify it's the same object - i2.SetString("67890", 10) - pool.Put(i2) - - i3 := pool.Get() - // Should be cleared again - if i3.String() != "0" { - t.Errorf("Expected cleared Int after second put, got %s", i3.String()) - } -} - -// TestExpModPooled tests pooled modular exponentiation -func TestExpModPooled(t *testing.T) { - pool := NewIntPool() - - base := []byte{0x02} // 2 - exp := []byte{0x0A} // 10 - mod := []byte{0x03, 0xE8} // 1000 - - result := ExpModPooled(pool, base, exp, mod) - - // 2^10 mod 1000 = 1024 mod 1000 = 24 - expected := []byte{0x18} // 24 - if !bytesEqual(result, expected) { - t.Errorf("ExpModPooled: expected %x, got %x", expected, result) - } -} - -// TestModExp tests the convenience function -func TestModExp(t *testing.T) { - base := []byte{0x02} - exp := []byte{0x0A} - mod := []byte{0x03, 0xE8} - - result, err := ModExp(base, exp, mod) - if err != nil { - t.Fatalf("ModExp failed: %v", err) - } - - expected := []byte{0x18} - if !bytesEqual(result, expected) { - t.Errorf("ModExp: expected %x, got %x", expected, result) - } - - // Test empty modulus case - result, err = ModExp(base, exp, []byte{}) - if err != nil { - t.Errorf("Expected no error for empty modulus, got %v", err) - } - if len(result) != 0 { - t.Errorf("Expected empty result for empty modulus, got %x", result) - } -} - - -// TestPreallocatedExpMod tests pre-allocated operations -func TestPreallocatedExpMod(t *testing.T) { - base := NewInt() - exp := NewInt() - mod := NewInt() - result := NewInt() - - base.SetString("2", 10) - exp.SetString("10", 10) - mod.SetString("1000", 10) - - PreallocatedExpMod(result, base, exp, mod) - - if result.String() != "24" { - t.Errorf("PreallocatedExpMod: expected 24, got %s", result.String()) - } -} - -// BenchmarkExpModPooled compares pooled vs non-pooled performance -func BenchmarkExpModPooled(b *testing.B) { - pool := NewIntPool() - base := []byte("123456789012345678901234567890") - exp := []byte("987654321098765432109876543210") - mod := []byte("111111111111111111111111111111") - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = ExpModPooled(pool, base, exp, mod) - } -} - -// BenchmarkExpModNonPooled benchmarks non-pooled operations for comparison -func BenchmarkExpModNonPooled(b *testing.B) { - base := []byte("123456789012345678901234567890") - exp := []byte("987654321098765432109876543210") - mod := []byte("111111111111111111111111111111") - - b.ResetTimer() - for i := 0; i < b.N; i++ { - baseInt := NewInt() - expInt := NewInt() - modInt := NewInt() - resultInt := NewInt() - - baseInt.SetBytes(base) - expInt.SetBytes(exp) - modInt.SetBytes(mod) - resultInt.ExpMod(baseInt, expInt, modInt) - _ = resultInt.Bytes() - } -} - - -// TestPoolConcurrency tests that the pool is safe for concurrent use -func TestPoolConcurrency(t *testing.T) { - pool := NewIntPool() - var wg sync.WaitGroup - - // Run many concurrent operations - for i := 0; i < 100; i++ { - wg.Add(1) - go func(n int) { - defer wg.Done() - - // Get and use an Int - num := pool.Get() - num.SetString("12345", 10) - - // Simulate some work - result := NewInt() - result.ExpMod(num, num, num) - - // Return to pool - pool.Put(num) - }(i) - } - - wg.Wait() -} - -// TestPoolWithBigNumbers tests pool with large numbers -func TestPoolWithBigNumbers(t *testing.T) { - pool := NewIntPool() - - // Test with 2048-bit numbers - base := make([]byte, 256) - exp := make([]byte, 256) - mod := make([]byte, 256) - - // Fill with test data - for i := range base { - base[i] = byte(i) - exp[i] = byte(255 - i) - mod[i] = 0xFF - } - mod[0] = 0x7F // Make sure modulus is not too large - - // This should not panic or leak memory - for i := 0; i < 10; i++ { - result := ExpModPooled(pool, base, exp, mod) - if len(result) == 0 { - t.Error("Expected non-empty result") - } - } -} - -// TestPoolComparison verifies pooled operations match non-pooled -func TestPoolComparison(t *testing.T) { - pool := NewIntPool() - - testCases := []struct { - base string - exp string - mod string - }{ - {"2", "10", "1000"}, - {"123456789", "987654321", "1000000007"}, - {"999999999999", "888888888888", "777777777777"}, - } - - for _, tc := range testCases { - // Non-pooled - base1 := NewInt() - exp1 := NewInt() - mod1 := NewInt() - result1 := NewInt() - - base1.SetString(tc.base, 10) - exp1.SetString(tc.exp, 10) - mod1.SetString(tc.mod, 10) - result1.ExpMod(base1, exp1, mod1) - - // Pooled - result2 := ExpModPooled(pool, base1.Bytes(), exp1.Bytes(), mod1.Bytes()) - - // Compare - if !bytesEqual(result1.Bytes(), result2) { - t.Errorf("Pooled vs non-pooled mismatch for %s^%s mod %s", tc.base, tc.exp, tc.mod) - } - - // Also compare with math/big - bigBase := new(big.Int) - bigExp := new(big.Int) - bigMod := new(big.Int) - bigResult := new(big.Int) - - bigBase.SetString(tc.base, 10) - bigExp.SetString(tc.exp, 10) - bigMod.SetString(tc.mod, 10) - bigResult.Exp(bigBase, bigExp, bigMod) - - if !bytesEqual(bigResult.Bytes(), result2) { - t.Errorf("Pooled vs math/big mismatch for %s^%s mod %s", tc.base, tc.exp, tc.mod) - } - } -} \ No newline at end of file