对于某些图形算法,我需要从数据库中获取大量记录到内存(~ 1M记录)。我希望快速完成这个任务,我希望记录成为对象(也就是说:我想要ORM)。为了粗略地对不同的解决方案进行基准测试,我创建了一个简单的问题,即一个包含1Mfoo对象的表,就像我在这里所做的:Why is loading SQLAlchemy objects via the ORM 5-8x slower than rows via a raw MySQLdb cursor?。
可以看到,使用裸SQL获取它们非常快;使用简单的for-循环将记录转换为对象也是快速的。两者都在2-3秒内执行.然而,使用ORM,如SQLAlchemy和Hibernate,这需要20-30秒:如果您问我,要慢得多,这只是一个简单的例子,没有关系和连接。
SQLAlchemy为自己提供了“成熟的、高性能的体系结构”(http://www.sqlalchemy.org/features.html)。类似于Hibernate“高性能”(http://hibernate.org/orm/)。在某种程度上,两者都是正确的,因为它们允许将非常通用的面向对象的数据模型来回映射到MySQL数据库。另一方面,它们是非常错误的,因为它们比SQL和本机代码慢10倍。就我个人而言,我认为他们可以做更好的基准测试来证明这一点,即与原生SQL + java或python相比的基准测试。但这不是眼前的问题。
当然,我不需要SQL +本机代码,因为它很难维护。因此,我想知道为什么不存在类似于处理数据库->对象映射本机的面向对象的数据库。有人建议OrientDB,所以我试了一下。API非常好:当您正确使用getter和setter时,对象是可插入的和可选择的。
但是我想要的不仅仅是API-甜度,所以我尝试了1M的例子:
import java.io.Serializable;
public class Foo implements Serializable {
public Foo() {}
public Foo(int a, int b, int c) { this.a=a; this.b=b; this.c=c; }
public int a,b,c;
public int getA() { return a; }
public void setA(int a) { this.a=a; }
public int getB() { return b; }
public void setB(int b) { this.b=b; }
public int getC() { return c; }
public void setC(int c) { this.c=c; }
}import com.orientechnologies.orient.object.db.OObjectDatabaseTx;
public class Main {
public static void insert() throws Exception {
OObjectDatabaseTx db = new OObjectDatabaseTx ("plocal:/opt/orientdb-community-1.7.6/databases/test").open("admin", "admin");
db.getEntityManager().registerEntityClass(Foo.class);
int N=1000000;
long time = System.currentTimeMillis();
for(int i=0; i<N; i++) {
Foo foo = new Foo(i, i*i, i+i*i);
db.save(foo);
}
db.close();
System.out.println(System.currentTimeMillis() - time);
}
public static void fetch() {
OObjectDatabaseTx db = new OObjectDatabaseTx ("plocal:/opt/orientdb-community-1.7.6/databases/test").open("admin", "admin");
db.getEntityManager().registerEntityClass(Foo.class);
long time = System.currentTimeMillis();
for (Foo f : db.browseClass(Foo.class).setFetchPlan("*:-1")) {
if(f.getA() == 345234) System.out.println(f.getB());
}
System.out.println("Fetching all Foo records took: " + (System.currentTimeMillis() - time) + " ms");
db.close();
}
public static void main(String[] args) throws Exception {
//insert();
fetch();
}
}使用OrientDB获取1M Foo大约需要18秒。使用getA()的for-循环是强制将对象字段实际加载到内存中,因为我注意到,在默认情况下,它们是延迟获取的。我猜这也可能是获取Foo的速度慢的原因,因为每次迭代都有db访问,而不是每次获取所有内容(包括字段)时都会访问db-access。
我试图使用setFetchPlan("*:-1")修复这个问题,我认为它也适用于字段,但这似乎不起作用。
问题:是否有一种快速的方法,最好是在2-3秒的范围内?为什么这需要18秒,而普通的SQL版本则需要3秒?
加载项:使用ODatabaseDocumentTX (如@frens)建议的加速比仅为5,但大约为2。调整以下代码给了我大约9秒的运行时间。这仍然比原始sql慢3倍,而没有执行到Foo的转换。几乎所有的时间都在循环中。
public static void fetch() {
ODatabaseDocumentTx db = new ODatabaseDocumentTx ("plocal:/opt/orientdb-community-1.7.6/databases/pits2").open("admin", "admin");
long time = System.currentTimeMillis();
ORecordIteratorClass<ODocument> it = db.browseClass("Foo");
it.setFetchPlan("*:0");
System.out.println("Fetching all Foo records took: " + (System.currentTimeMillis() - time) + " ms");
time = System.currentTimeMillis();
for (ODocument f : it) {
//if((int)f.field("a") == 345234) System.out.println(f.field("b"));
}
System.out.println("Iterating all Foo records took: " + (System.currentTimeMillis() - time) + " ms");
db.close();
}发布于 2014-07-17 19:47:53
答案在于方便。
在一次面试中,当我问一位候选人对LINQ (我知道的C#,但与你的问题相关)的看法时,他们很正确地回答说,这是对性能的牺牲,而不是方便。
手写的SQL语句(不管它是否调用存储过程)总是比使用ORM更快,ORM自动将查询的结果转换为好的、易于使用的POCOs。
也就是说,差别不应该像你所经历的那么大。是的,用自动魔法的方式做这件事是有开销的,但它不应该那么好。我在这里确实有经验,在C#中,我不得不使用特殊的反射类来减少完成这种自动魔法映射所需的时间。
有了大量的数据,我预计ORM会出现最初的减速,但那将是可以忽略不计的。3秒到18秒是巨大的。
发布于 2014-07-18 01:14:46
如果您分析了您的测试,您会发现大约60-80%的CPU时间是通过执行以下四种方法来完成的:
所以是的,在这个设置中瓶颈在ORM层。使用ODatabaseDocumentTx提供了大约5倍的加速比。也许能帮你找到你想去的地方。
仍然有很多时间(接近50%)花在com.orientechnologies...OJNADirectMemory.getInt(...).上。从内存位置读取整数是很昂贵的。不明白为什么这里不使用java nio字节缓冲区。节省了大量跨越Java /本机边界等。
除了这些微观基准和OrientDB的卓越行为之外,我认为至少还有两件事需要考虑:
在进行基准测试之前,我建议您将代码热身。
发布于 2015-04-13 20:09:55
你在这里做的是最坏的情况。正如您为您的数据库编写(或者应该编写)的那样,您的测试只是读取一个表并将其直接写入一个流中。
所以你看到的是魔法的全部开销。通常,如果你做一些更复杂的事情,比如加入、选择、过滤和排序,你的ORM的开销就会降低到5%到10%的合理比例。
另一件您应该考虑的事情--我猜orient也在做同样的事情-- ORM解决方案是创建新对象,增加内存消耗,Java在内存消耗方面真的很糟糕,以及为什么我在处理大量数据/对象时一直在内存表中使用自定义。
您知道对象在哪里是表中的一行。
您的对象还会被插入到列表/映射中(至少Hibernate正在这样做)。一旦你改变了这些物体,它就会跟踪它们的脏度。这个插入也需要很长的时间来重新分配,这也是我们使用分页列表或映射的原因之一。如果该区域增长,复制1M引用是非常缓慢的。
https://stackoverflow.com/questions/24812180
复制相似问题