环境
Windows 11,AMD R7 8核 16 线程处理器,16GB内存。
测试内容
随机写入1000w条账户数据。然后从写入的1000w条数据随机读取1000,10000,100000,1000000条数据。
测试结果
写入结果
随机写入1000w条数据,总花费时间350s左右,所以写入的TPS大概为 10000000 / 350 == 28571TPS
这是随机写入各个阶段分别花费的时间
在内存中构建整颗trie内容:TryUpdate spend time 24.3511507s
对构建的trie进行哈希计算:Hash spend time 29.2529906s
将内存中的trie提交到trie对应的内存数据库中:Commit spend time 93.6990205s
将内存数据库中的数据提交到leveldb:Commit to leveldb spend time 201.5279795s
读取结果
这是随机读取1000,10000,100000,1000000条数据所花费的时间
读取 1000 条,花费时间 1.19s,此时 840TPS
读取 10000 条,花费时间 8.26s,此时 1210TPS
读取 100000 条,花费时间 54.34s,此时 1840TPS
读取 1000000 条,花费时间 330s,此时 3030TPS
从上面可以看出,当一次性读取的数据越多的时候,TPS越高,因为读出来的中间节点已经在内存中缓存起来了的缘故。当从leveldb中加载到内存中再次读取的时候,读取的时间几乎可以忽略不记。比如读取10w条完毕之后,再把这10w条读一次加起来的时间跟第一次读取10w条的数据一摸一样。所以假设真实情景读取时按照从缓存读取一半从leveldb读取一半来计算,理论TPS大概是2000左右。
其他
1000w条写完之后大概占用内存为1.7GB左右。写入的数据一个账户的 key 为32字节,账户的数据大概为 100 字节。一共需要1.32GB,与1.7GB大概相差380MB左右。
测试代码
写入测试:
func IntToByte(num int) []byte {
data := int64(num)
var buffer bytes.Buffer
binary.Write(&buffer, binary.BigEndian, data)
return buffer.Bytes()
}
// 10000000 count account
// TryUpdate spend time 24.3511507s
// Hash spend time 29.2529906s
// Commit spend time 93.6990205s
// Commit to leveldb spend time 201.5279795s
// root 0x624b8710c478c9dd60d627b308a30d3c6e59f7199b2586a8e31af8bbab254efd
// 10000000 / 360 == 27777
func TestTpsWrite(t *testing.T) {
usr, _ := user.Current()
dir := filepath.Join(usr.HomeDir, "trie-tps")
dbPath := path.Join(dir, "trie")
diskdb, _ := leveldb.New(dbPath, 256, 0, "", false)
trie, _ := New(emptyRoot, NewDatabase(diskdb))
count := 10000000
addresses := make([][32]byte, count)
for i := 0; i < len(addresses); i++ {
data := IntToByte(i)
copy(addresses[i][:], crypto.Keccak256(data))
}
random := rand.New(rand.NewSource(0))
accounts := make([][]byte, len(addresses))
for i := 0; i < len(accounts); i++ {
var (
nonce = uint64(random.Int63())
root = emptyRoot
code = emptyState
)
numBytes := random.Uint32() % 33 // [0, 32] bytes
balanceBytes := make([]byte, numBytes)
random.Read(balanceBytes)
balance := new(big.Int).SetBytes(balanceBytes)
data, _ := rlp.EncodeToBytes(&account{nonce, balance, root, code[:]})
accounts[i] = data
}
t.Logf("%d count account", count)
var start time.Time
var cost time.Duration
start = time.Now()
for i := 0; i < count; i++ {
trie.TryUpdate(addresses[i][:], accounts[i])
}
cost = time.Since(start)
t.Logf("TryUpdate spend time %s", cost)
start = time.Now()
trie.Hash()
cost = time.Since(start)
t.Logf("Hash spend time %s", cost)
start = time.Now()
root, _, _ := trie.Commit(nil)
cost = time.Since(start)
t.Logf("Commit spend time %s", cost)
start = time.Now()
trie.db.Commit(root, false, nil)
cost = time.Since(start)
t.Logf("Commit to leveldb spend time %s", cost)
t.Logf("root %s", root.String())
trie.db.diskdb.(*leveldb.Database).Close()
}
读取测试(需要更改count测试读取结果):
// 1000 1.19s 840TPS
// 10000 8.26s 1210TPS
// 100000 54.34s 1840TPS
// 1000000 330s 3030TPS
func TestTpsRead(t *testing.T) {
usr, _ := user.Current()
dir := filepath.Join(usr.HomeDir, "trie-tps")
dbPath := path.Join(dir, "trie")
diskdb, _ := leveldb.New(dbPath, 256, 0, "", false)
trie, _ := New(common.HexToHash("0x624b8710c478c9dd60d627b308a30d3c6e59f7199b2586a8e31af8bbab254efd"), NewDatabase(diskdb))
count := 100000
addresses := make([][32]byte, count)
for i := 0; i < len(addresses); i++ {
data := IntToByte(i)
copy(addresses[i][:], crypto.Keccak256(data))
}
t.Logf("%d count account", count)
var start time.Time
var cost time.Duration
start = time.Now()
for i := 0; i < count; i++ {
trie.TryGet(addresses[i][:])
}
cost = time.Since(start)
t.Logf("TryGet spend time %s", cost)
trie.db.diskdb.(*leveldb.Database).Close()
}