一次无聊的性能测试

分享实验性能CGoJavaErlang by 达达 at 2011-06-07

这个周末,对C、Go、java、erlang做了一次无聊的性能测试。一开始只是在想了解用Go和C实现一个简单的内存数据库性能会差多少,当然不可能完整实现一个内存数据库再测试,于是我模拟了一个最简单的实验:一个内存数据库,库里面有一个表,表的每条记录固定是8个int类型的字段,不考虑动态性,不考虑查询,只做单纯的插入操作,看看执行1w次插入要多少时间。

用Go和C分别实现了代码以后,我又想到跟erlang的ets插入速度对比如何呢?用Java实现性能又如何呢?下面是测试用的代码和实验结果。

C的实现(用--std=c99参数编译):

#include <stdio.h> 
#include <stdlib.h> 
#include <time.h> 
 
typedef struct s_mdb_table mdb_table;
typedef struct s_mdb_row mdb_row;
 
struct s_mdb_table {
    char* name;
    mdb_row* head;
};
 
struct s_mdb_row {
    mdb_row* prev;
    mdb_row* next;
 
    int field_1;
    int field_2;
    int field_3;
    int field_4;
    int field_5;
    int field_6;
    int field_7;
    int field_8;
};
 
mdb_table* mdb_new(char* name) {
    mdb_table* table = (mdb_table*)malloc(sizeof(mdb_table));
    table->name = name;
    table->head = NULL;
 
    return table;
}
 
void mdb_insert(mdb_table* table, mdb_row* row) {
    row->next = table->head;
 
    if (table->head != NULL) {
        table->head->prev = row;
    }
 
    table->head = row;
}
 
int main (int argc, char** argv) {
    mdb_table* table = mdb_new("test_table");
 
    clock_t t1 = clock();
 
    for (int i = 0; i < 10000; i ++) {
        mdb_row* row = (mdb_row*)malloc(sizeof(mdb_row));
 
        mdb_insert(table, row);
    }
 
    clock_t t2 = clock();
 
    printf("%fsn", (double)(t2 - t1) / CLOCKS_PER_SEC);
 
    return 0;
}

Go的实现:

package main
 
import (
    "fmt" 
    "time" 
)
 
type mdb_table struct {
    name string 
    head *mdb_row
}
 
type mdb_row struct {
    prev *mdb_row
    next *mdb_row
 
    field_1 int 
    field_2 int 
    field_3 int 
    field_4 int 
    field_5 int 
    field_6 int 
    field_7 int 
    field_8 int 
}
 
func (table *mdb_table) insert(row *mdb_row) {
    row.next = table.head
 
    if table.head != nil {
        table.head.prev = row
    }
 
    table.head = row
}
 
func main() {
    table := new(mdb_table)
    table.name = "test_table";
    table.head = nil;
 
    t1 := time.Nanoseconds()
 
    for i := 0; i < 10000; i ++ {
        row := new(mdb_row)
 
        table.insert(row)
    }
 
    t2 := time.Nanoseconds()
 
    fmt.Printf("%fsn", float32(t2 - t1) / 1000000000)
}

Java的实现:

public class memdb
{
    public static void main (String[] args)
    {
        DbTable table = new DbTable("test_table");
 
        long t1 = System.nanoTime();
 
        for (int i = 0; i < 10000; i ++)
        {
            DbRow row = new DbRow();
 
            table.Insert(row);
        }
 
        long t2 = System.nanoTime();
 
        System.out.printf("%fsn", (float)(t2 - t1) / 1000000000);
    }
}
 
class DbTable
{
    public DbTable (String name)
    {
        this.name = name;
        this.head = null;
    }
 
    public void Insert (DbRow row)
    {
        row.SetNext(this.head);
 
        if (this.head != null)
            this.head.SetPrev(row);
 
        this.head = row;
    }
 
    private String name;
    private DbRow head;
}
 
class DbRow
{
    public DbRow ()
    {
    }
 
    public void SetPrev(DbRow prev)
    {
        this.prev = prev;
    }
 
    public void SetNext(DbRow next)
    {
        this.next = next;
    }
 
    private DbRow prev;
    private DbRow next;
 
    public int field_1;
    public int field_2;
    public int field_3;
    public int field_4;
    public int field_5;
    public int field_6;
    public int field_7;
    public int field_8;
}
 
class DbData
{
    public int field_1;
    public int field_2;
    public int field_3;
    public int field_4;
    public int field_5;
    public int field_6;
    public int field_7;
    public int field_8;
}

erlang的实现:

-module(memdb). 
-export([run/0, test/0]). 
 
run() -> 
    {Time, _} = timer:tc(?MODULE, test, []),
    io:format("~ps~n", [Time / 1000]). 
 
test() -> 
    Table = ets:new(test_table),
    insert(Table, 0). 
 
insert(Table, 10000) -> 
    ok;
insert(Table, N) -> 
    ets:insert(Table, {N, 1, 2, 3, 4, 5, 6, 7}),
    insert(Table, N + 1). 

测试脚本:

#!/bin/sh 
 
echo "C: $(./a.out)" 
echo "GO: $(./6.out)" 
echo "Java: $(java memdb)" 
echo "Erlang: $(erl -noshell -s memdb run -s init stop)" 

在我的电脑上的一次典型测试结果:

C: 0.000669s
GO: 0.003167s
Java: 0.003626s
Erlang: 0.024s

C毫无悬念的第一,Go和Java的性能相当,Go稍胜一筹,这个测试里面,其实比的是内存申请的速度,如果把内存申请的操作提前,后面1万次循环只对数据赋值,那根本没什么消耗,并且erlang用的是完整的内存数据库实现参与测试,里面包含了hash算法等耗性能的东西,这样测其实很不公平,所以我说这是一个很无聊的性能测试,呵呵。