mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
The matching engine (pkg/lx, 45.5K LOC incl. tests) and the deterministic core layered on it (pkg/dex) are one matcher spread across two packages (dex imported lx in 12 files; lx imported neither). Fold lx into dex and drop the "lx" name entirely — pkg/dex is now the single matcher package. Pure rename/fold, ZERO logic change. Behavior proven identical: the merged pkg/dex runs 457 top-level tests (431 former-lx + 26 former-dex) and 489 test funcs (468 + 21), all green — matching, batch (BatchMatchEqualsSequential, run-to-run determinism, tile boundary), GPU/CPU parity, truncation, and CL swap math all pass unchanged. What moved: - git mv all 101 pkg/lx/*.go + 1 .c into pkg/dex/ (package lx -> package dex). - lx's swap_test.go -> swap_math_test.go (only filename collision with dex's existing swap_test.go). - Rewrote imports repo-wide: 54 pkg/lx import lines removed; 42 external importers (api, client, consensus, dchain, dpdk, lxgpu, marketdata, test/, tests/) repointed to pkg/dex with lx.X -> dex.X; the 12 in-package dex files had lx.X -> bare X. 717 selector lines rewritten. Stale pkg/lx comment refs updated (pkg/lxgpu left intact). Collisions resolved (2, both package-level vars): - ErrNoLiquidity: lx's pool-level error (dexError, used only in swap.go, never referenced via qualifier) renamed ErrPoolNoLiquidity to match the existing ErrPool* convention; dex's router-level ErrNoLiquidity (public, has errors.Is tests) kept. - priceMultiplierBig: identical after fold (both big.NewInt(PriceMultiplier)); deduped by deleting dex's copy. Not folded — pkg/orderbook: it is NOT the GPU seam (that is orderbook_gpu.go, which lives in the engine and folded in here). pkg/orderbook is a zero-importer, placeholder-cgo island whose 13 core domain identifiers (Order, OrderBook, Trade, OrderType, OrderStatus, PriceLevel, Buy, Sell, Market, Limit, Filled, PartiallyFilled, NewOrderBook) collide incompatibly with the engine's. Folding it verbatim breaks the build; folding via rename reintroduces the exact parallel duplication this fold removes. Left for owner decision (recommend deletion as dead code) — see refactor notes. Follow-on (separate repos, not touched): 19 files under luxfi/dex import pkg/lx externally — lux/precompile/dex, lux/precompile-dex/dex, lux/consensus/examples/01-simple-bridge, lux/_consensus-v12537/examples, lux/cex/pkg/engine — repoint to pkg/dex after this lands. Verified: CGO_ENABLED=0 GOWORK=off go build ./... clean; go vet ./... clean; go test ./... -count=1 all green (22 ok / 0 fail). Co-authored-by: Hanzo Dev <[email protected]>
184 lines
5.1 KiB
Go
184 lines
5.1 KiB
Go
package benchmark
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/luxfi/crypto"
|
|
"github.com/luxfi/dex/pkg/dex"
|
|
"github.com/luxfi/geth/common"
|
|
)
|
|
|
|
// mustSignOrder builds a SignedOrder with a fresh secp256k1 keypair, signs
|
|
// the SigningHash digest, and stamps the derived address as Sender.
|
|
// Used by BenchmarkBatchVerifyOrders.
|
|
func mustSignOrder(b testing.TB, id uint64) dex.SignedOrder {
|
|
b.Helper()
|
|
key, err := crypto.GenerateKey()
|
|
if err != nil {
|
|
b.Fatalf("generate key: %v", err)
|
|
}
|
|
pub := crypto.FromECDSAPub(&key.PublicKey)
|
|
var addr common.Address
|
|
copy(addr[:], crypto.Keccak256(pub[1:])[12:])
|
|
|
|
so := dex.SignedOrder{
|
|
Order: dex.Order{
|
|
ID: id,
|
|
Symbol: "BTC-USD",
|
|
Type: dex.Limit,
|
|
Side: dex.Buy,
|
|
Price: 100 + float64(id%100)/10,
|
|
Size: 1 + float64(id%5),
|
|
ClientID: fmt.Sprintf("c-%d", id),
|
|
},
|
|
Sender: addr,
|
|
}
|
|
hash, err := so.SigningHash()
|
|
if err != nil {
|
|
b.Fatalf("signing hash: %v", err)
|
|
}
|
|
sig, err := crypto.Sign(hash[:], key)
|
|
if err != nil {
|
|
b.Fatalf("sign: %v", err)
|
|
}
|
|
copy(so.Sig[:], sig)
|
|
return so
|
|
}
|
|
|
|
// BenchmarkOrderBook benchmarks order processing
|
|
func BenchmarkOrderBook(b *testing.B) {
|
|
ob := dex.NewOrderBook("BENCH-USD")
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
order := &dex.Order{
|
|
ID: uint64(i + 1),
|
|
Symbol: "BENCH-USD",
|
|
Type: dex.Limit,
|
|
Side: dex.Side(i % 2),
|
|
Price: 100 + float64(i%100)/10,
|
|
Size: 1,
|
|
UserID: "bench",
|
|
}
|
|
|
|
ob.AddOrder(order)
|
|
}
|
|
|
|
ordersPerSecond := float64(b.N) / b.Elapsed().Seconds()
|
|
b.ReportMetric(ordersPerSecond, "orders/sec")
|
|
b.ReportMetric(float64(b.Elapsed().Nanoseconds())/float64(b.N), "ns/order")
|
|
}
|
|
|
|
// BenchmarkOrderBookParallel benchmarks parallel order processing
|
|
func BenchmarkOrderBookParallel(b *testing.B) {
|
|
ob := dex.NewOrderBook("PARALLEL-USD")
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
i := 0
|
|
for pb.Next() {
|
|
order := &dex.Order{
|
|
ID: uint64(i + 1),
|
|
Symbol: "PARALLEL-USD",
|
|
Type: dex.Limit,
|
|
Side: dex.Side(i % 2),
|
|
Price: 100 + float64(i%100),
|
|
Size: 1,
|
|
UserID: fmt.Sprintf("user_%d", i%10),
|
|
}
|
|
ob.AddOrder(order)
|
|
i++
|
|
}
|
|
})
|
|
|
|
ordersPerSecond := float64(b.N) / b.Elapsed().Seconds()
|
|
b.ReportMetric(ordersPerSecond, "orders/sec")
|
|
b.ReportMetric(float64(b.Elapsed().Nanoseconds())/float64(b.N), "ns/order")
|
|
}
|
|
|
|
// BenchmarkBatchVerifyOrders compares per-order CPU ecrecover against the
|
|
// GPU-batched path on 10k orders. The CPU side is the same per-order oracle
|
|
// the test uses as a reference; the "GPU" side calls the batch entry point
|
|
// (luxcpp/crypto cgo dispatch when cgo is enabled, otherwise the CPU oracle).
|
|
func BenchmarkBatchVerifyOrders(b *testing.B) {
|
|
const N = 10_000
|
|
|
|
// Build the corpus once outside the timer — signing 10k orders with
|
|
// fresh keys takes seconds and would dominate every iteration.
|
|
orders := make([]dex.SignedOrder, N)
|
|
for i := 0; i < N; i++ {
|
|
orders[i] = mustSignOrder(b, uint64(i+1))
|
|
}
|
|
|
|
b.Run("CPU_PerOrder", func(b *testing.B) {
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
// Single-element batches → forces per-order dispatch.
|
|
for j := range orders {
|
|
if _, err := dex.BatchVerifyOrders(orders[j : j+1]); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
}
|
|
b.ReportMetric(float64(b.N*N)/b.Elapsed().Seconds(), "verifies/sec")
|
|
})
|
|
|
|
b.Run("GPU_Batch", func(b *testing.B) {
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
if _, err := dex.BatchVerifyOrders(orders); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
b.ReportMetric(float64(b.N*N)/b.Elapsed().Seconds(), "verifies/sec")
|
|
})
|
|
}
|
|
|
|
// BenchmarkBatchAMM measures the CPU constant-product (xy=k) batch evaluation
|
|
// over 100k tuples. The AMM curve runs on-chain in the EVM/consensus; there is
|
|
// no GPU acceleration for it (the GPU path was the order-book matcher only).
|
|
func BenchmarkBatchAMM(b *testing.B) {
|
|
const N = 100_000
|
|
reserves := make([]dex.ReservePair, N)
|
|
amounts := make([]uint64, N)
|
|
for i := 0; i < N; i++ {
|
|
reserves[i] = dex.ReservePair{
|
|
ReserveX: uint64(1+i) * 1_000_000,
|
|
ReserveY: uint64(2+i) * 500_000,
|
|
}
|
|
amounts[i] = uint64(1 + i%10_000)
|
|
}
|
|
|
|
b.Run("CPU_Loop", func(b *testing.B) {
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
if _, err := dex.BatchEvalConstantProductCPU(reserves, amounts); err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
}
|
|
b.ReportMetric(float64(b.N*N)/b.Elapsed().Seconds(), "evals/sec")
|
|
})
|
|
}
|
|
|
|
// LP-108: BenchmarkMLXEngine and BenchmarkPlanetScale removed.
|
|
//
|
|
// BenchmarkMLXEngine pretended to measure an MLX-accelerated engine
|
|
// but called the standard CPU OrderBook and reported a hardcoded
|
|
// `b.ReportMetric(597, "ns/order")` literal — a fabricated number,
|
|
// not a measurement. The "MLX engine" itself
|
|
// (pkg/engine/mlx_engine.go) was a `time.Sleep` stub also archived
|
|
// under archive/lp108-2026-05-04/.
|
|
//
|
|
// BenchmarkPlanetScale reported entirely made-up metrics (markets=5M,
|
|
// orders/sec=150M, ns/order=597, watts=370, orders/watt=405405) that
|
|
// were never measured by any code in this repo.
|
|
//
|
|
// Honest CPU benchmarks remain at BenchmarkOrderBook /
|
|
// BenchmarkOrderBookParallel above. Real C++ numbers are at
|
|
// luxcpp/dex/build/luxdex_bench (5.51 M orders/sec single-symbol).
|