0

我正在测试 BerkeleyDB Java 版以了解我是否可以在我的项目中使用它。

我创建了一个非常简单的程序,它适用于 com.sleepycat.je.Database 类的对象:

  • 写入 N 条记录,每条 5-15kb,生成的键如 Integer.toString(random.nextInt());

  • 读取这些记录并使用方法 Database#get 按照它们创建的顺序获取它们;

  • 使用方法 Database#get 以随机顺序读取相同数量的记录。

我现在看到了奇怪的事情。第三次测试的执行时间随着记录数的增加呈非线性增长。

  • N=80000,写入=55sec,顺序读取=17sec,随机读取=3sec
  • N=100000,写入=60sec,顺序读取=20sec,随机读取=7sec
  • N=120000,写入=68sec,顺序读取=27sec,随机读取=11sec
  • N=140000,写入=82sec,顺序读取=32sec,随机读取=47sec

(当然,我已经多次运行测试。)

我想我做错了什么。这是参考的来源(对不起,它有点长),方法的调用顺序相同:

private Environment env;
private Database db;
private Random random = new Random();
private List<String> keys = new ArrayList<String>();
private int seed = 113;


public boolean dbOpen() {
    EnvironmentConfig ec = new EnvironmentConfig();
    DatabaseConfig dc = new DatabaseConfig();
    ec.setAllowCreate(true);
    dc.setAllowCreate(true);
    env = new Environment(new File("mydbenv"), ec);
    db = env.openDatabase(null, "moe", dc);
    return true;
}

public int storeRecords(int i) {
    int j;
    long size = 0;
    DatabaseEntry key = new DatabaseEntry();
    DatabaseEntry val = new DatabaseEntry();

    random.setSeed(seed);

    for (j = 0; j < i; j++) {
        String k = Long.toString(random.nextLong());
        byte[] data = new byte[5000 + random.nextInt(10000)];
        keys.add(k);

        size += data.length;

        random.nextBytes(data);
        key.setData(k.getBytes());
        val.setData(data);
        db.put(null, key, val);
    }

    System.out.println("GENERATED SIZE: " + size);

    return j;
}                   

public int fetchRecords(int i) {
    int j, res;
    DatabaseEntry key = new DatabaseEntry();
    DatabaseEntry val = new DatabaseEntry();

    random.setSeed(seed);
    res = 0;

    for (j = 0; j < i; j++) {
        String k = Long.toString(random.nextLong());
        byte[] data = new byte[5000 + random.nextInt(10000)];
        random.nextBytes(data);
        key.setData(k.getBytes());
        db.get(null, key, val, null);
        if (Arrays.equals(data, val.getData())) {
            res++;
        } else {
            System.err.println("FETCH differs: " + j);
            System.err.println(data.length + " " + val.getData().length);
        }
    }

    return res;
}

public int fetchRandom(int i) {
    DatabaseEntry key = new DatabaseEntry();
    DatabaseEntry val = new DatabaseEntry();

    for (int j = 0; j < i; j++) {
        String k = keys.get(random.nextInt(keys.size()));
        key.setData(k.getBytes());
        db.get(null, key, val, null);
    }

    return i;
}
4

1 回答 1

1

性能下降是非线性的,原因有两个:

  1. BDB-JE 数据结构是一个 b-tree,它在检索一条记录时具有 O(log(n)) 的性能。通过 get 方法检索所有内容是 O(n*log(n))。
  2. 大型数据集不适合 RAM,因此磁盘访问会减慢一切。随机访问的缓存局部性很差。

请注意,您可以通过放弃一些持久性来提高写入性能: ec.setTxnWriteNoSync(true);

您可能还想尝试 Tupl,这是我一直在研究的开源 BerkeleyDB 替代品。它仍处于 alpha 阶段,但您可以在 SourceForge 上找到它。

为了公平比较 BDB-JE 和 Tupl,我将缓存大小设置为 500M,并在存储方法结束时执行显式检查点。

使用 BDB-JE:

  • N=80000,写入=11.0sec,读取=5.3sec
  • N=100000,写入=13.6sec,读取=7.0sec
  • N=120000,写入=16.4 秒,读取=29.5 秒
  • N=140000,写入=18.8sec,读取=35.9sec
  • N=160000,写入=21.5sec,读取=41.3sec
  • N=180000,写入=23.9 秒,读取=46.4 秒

使用 Tupl:

  • N=80000,写入=21.7sec,读取=4.4sec
  • N=100000,写入=27.6sec,读取=6.3sec
  • N=120000,写入=30.2sec,读取=8.4sec
  • N=140000,写入=35.4sec,读取=12.2sec
  • N=160000,写入=39.9 秒,读取=17.4 秒
  • N=180000,写入=45.4sec,读取=22.8sec

由于其基于日志的格式,BDB-JE 写入条目的速度更快。然而,Tupl 的阅读速度更快。以下是 Tupl 测试的来源:

导入 java.io。; 导入 java.util。;

导入 org.cojen.tupl.*;

公共类 TuplTest { public static void main(final String[] args) 抛出异常 { final RandTupl rt = new RandTupl(); rt.dbOpen(args[0]);

    {
        long start = System.currentTimeMillis();
        rt.storeRecords(Integer.parseInt(args[1]));
        long end = System.currentTimeMillis();
        System.out.println("store duration: " + (end - start));
    }

    {
        long start = System.currentTimeMillis();
        rt.fetchRecords(Integer.parseInt(args[1]));
        long end = System.currentTimeMillis();
        System.out.println("fetch duration: " + (end - start));
    }
}

private Database db;
private Index ix;
private Random random = new Random();
private List<String> keys = new ArrayList<String>();
private int seed = 113;

public boolean dbOpen(String home) throws Exception {
    DatabaseConfig config = new DatabaseConfig();
    config.baseFile(new File(home));
    config.durabilityMode(DurabilityMode.NO_FLUSH);
    config.minCacheSize(500000000);
    db = Database.open(config);
    ix = db.openIndex("moe");
    return true;
}

public int storeRecords(int i) throws Exception {
    int j;
    long size = 0;

    random.setSeed(seed);

    for (j = 0; j < i; j++) {
        String k = Long.toString(random.nextLong());
        byte[] data = new byte[5000 + random.nextInt(10000)];
        keys.add(k);

        size += data.length;

        random.nextBytes(data);
        ix.store(null, k.getBytes(), data);
    }

    System.out.println("GENERATED SIZE: " + size);

    db.checkpoint();
    return j;
}

public int fetchRecords(int i) throws Exception {
    int j, res;

    random.setSeed(seed);
    res = 0;

    for (j = 0; j < i; j++) {
        String k = Long.toString(random.nextLong());
        byte[] data = new byte[5000 + random.nextInt(10000)];
        random.nextBytes(data);
        byte[] val = ix.load(null, k.getBytes());
        if (Arrays.equals(data, val)) {
            res++;
        } else {
            System.err.println("FETCH differs: " + j);
            System.err.println(data.length + " " + val.length);
        }
    }

    return res;
}

public int fetchRandom(int i) throws Exception {
    for (int j = 0; j < i; j++) {
        String k = keys.get(random.nextInt(keys.size()));
        ix.load(null, k.getBytes());
    }

    return i;
}

}

于 2012-07-08T02:50:35.513 回答