remove gmp_pool

This commit is contained in:
Kevaundray Wedderburn 2025-07-08 16:57:58 +01:00
parent c521fa6bdb
commit 7fc1617ac1
4 changed files with 1 additions and 355 deletions

View file

@ -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)
}
})
})
}
}

View file

@ -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

View file

@ -1,96 +0,0 @@
package gmp
// #include <gmp.h>
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)
}

View file

@ -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)
}
}
}