mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 06:06:44 +00:00
remove gmp_pool
This commit is contained in:
parent
c521fa6bdb
commit
7fc1617ac1
4 changed files with 1 additions and 355 deletions
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto/modexp/bigint"
|
"github.com/ethereum/go-ethereum/crypto/modexp/bigint"
|
||||||
gmpcwrapper "github.com/ethereum/go-ethereum/crypto/modexp/gmp/cwrapper"
|
gmpcwrapper "github.com/ethereum/go-ethereum/crypto/modexp/gmp/cwrapper"
|
||||||
gmpgeneric "github.com/ethereum/go-ethereum/crypto/modexp/gmp/generic"
|
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
|
// generateWorstCase generates a byte array with all bits set to 1
|
||||||
|
|
@ -47,7 +46,6 @@ func BenchmarkComprehensive(b *testing.B) {
|
||||||
{"BigInt", bigint.ModExp},
|
{"BigInt", bigint.ModExp},
|
||||||
{"GMPGeneric", gmpgeneric.ModExp},
|
{"GMPGeneric", gmpgeneric.ModExp},
|
||||||
{"GMPCWrapper", gmpcwrapper.ModExp},
|
{"GMPCWrapper", gmpcwrapper.ModExp},
|
||||||
{"GMPPooled", gmppool.ModExp},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|
@ -100,7 +98,6 @@ func BenchmarkWorstCaseOnly(b *testing.B) {
|
||||||
{"BigInt", bigint.ModExp},
|
{"BigInt", bigint.ModExp},
|
||||||
{"GMPGeneric", gmpgeneric.ModExp},
|
{"GMPGeneric", gmpgeneric.ModExp},
|
||||||
{"GMPCWrapper", gmpcwrapper.ModExp},
|
{"GMPCWrapper", gmpcwrapper.ModExp},
|
||||||
{"GMPPooled", gmppool.ModExp},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Run("AllFF", func(b *testing.B) {
|
b.Run("AllFF", func(b *testing.B) {
|
||||||
|
|
@ -147,7 +144,6 @@ func BenchmarkMemoryProfile(b *testing.B) {
|
||||||
{"BigInt", bigint.ModExp},
|
{"BigInt", bigint.ModExp},
|
||||||
{"GMPGeneric", gmpgeneric.ModExp},
|
{"GMPGeneric", gmpgeneric.ModExp},
|
||||||
{"GMPCWrapper", gmpcwrapper.ModExp},
|
{"GMPCWrapper", gmpcwrapper.ModExp},
|
||||||
{"GMPPooled", gmppool.ModExp},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, size := range sizes {
|
for _, size := range sizes {
|
||||||
|
|
@ -230,12 +226,6 @@ func BenchmarkComparison(b *testing.B) {
|
||||||
_, _ = gmpcwrapper.ModExp(data.base, data.exp, data.mod)
|
_, _ = 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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.
|
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)
|
### Byte Array Interface (Recommended)
|
||||||
|
|
||||||
|
|
@ -55,18 +55,6 @@ result.ExpMod(base, exp, mod)
|
||||||
fmt.Printf("Result: %s\n", result)
|
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
|
## Testing
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue