登录 用户中心() [退出] 后台管理 注册
   
您的位置: 首页 >> 程序员学前班[不再更新,只读] >> 主题: [pgsql]转贴一个非常有趣的性能问题     [回主站]     [分站链接]
标题
[pgsql]转贴一个非常有趣的性能问题
clq
浏览(0) + 2008-07-07 13:53:39 发表 编辑

关键字:

[pgsql]转贴一个非常有趣的性能问题

从中倒是可以得到不少启示. 也可以用在别的数据库操作中.
--------------------------------------------------

http://bbs.pgsqldb.com/index.php?t=msg&th=11159&start=0&rid=&S=c2494d9ba57e763c2c9ed54b3037a887

计算机科学 » PostgreSQL的论坛 » 请教关于libpq的使用,内附测试代码及测试结果 【续】
显示: 今天的主题 :: 调查列表
发送邮件给朋友

作者 主题
seth.Yang
Private First Class



发贴数: 10
注册时间:
五月 2008
请教关于libpq的使用,内附测试代码及测试结果 【续】 三, 18 六月 2008 16:25

几日之前曾经请教过《请教关于libpq的使用,内附测试代码及测试结果》详见http://bbs.pgsqldb.com/index.php?t=msg&th=11114&start=0&rid=&S=056054310e9f2f0b432592d39810ffd4,并得到阿弟的热心回答。今天继续该问题提问:
【问题背景】上次说到,在c代码中用 PQExce ("BEGIN") 和 PQExce ("END")组成事务,速度提高到2秒完成20000条出入,代码如下:


/* insert5.c */
#include
#include
#include
#include "libpq-fe.h"

int main(int argc, char *argv[])
{
char sql[] = "INSERT INTO tb_test (id, name) VALUES ($1, $2)";
PGconn *conn;
const char *stmtName = "stmt";
const char *values[] = {"1", "test"};
int i;
PGresult *ret;
time_t tp1, tp2;
char *msg;
int flag;

flag = 1;
conn = PQconnectdb ("hostaddr=host port=5432 dbname=xxx user=yyy password=******");
time (&tp1);
ret = PQprepare (conn, stmtName, sql, 2, NULL);
msg = PQresultErrorMessage (ret);
PQclear (ret);
if (strlen (msg) > 0)
{
printf ("prepared faild\n");
return -1;
}
ret = PQexec (conn, "BEGIN");
msg = PQresultErrorMessage (ret);
PQclear (ret);
if (strlen (msg) > 0)
{
printf ("transaction begin faile caused by %s\n", msg);
PQfinish (conn);
return -1;
}
for (i = 0; i < 20000; i ++)
{
ret = PQexecPrepared (conn, stmtName, 2, values, NULL, NULL, 0);
msg = PQresultErrorMessage (ret);
PQclear (ret);
if (strlen (msg) > 0)
{
printf ("execute prepared statemnt faild at line %d caused by %s\n", i, msg);
PQexec (conn, "ROLLBACK");
flag = 0;
}
}
if ( flag )
PQexec (conn, "END");
time (&tp2);
printf ("execute time: %d second(s)\n", tp2 - tp1);
PQfinish (conn);
return 0;
}

$gcc -I/usr/include/ -I/usr/local/postgres/include/ -L/usr/local/postgres/lib/ -llibpg -o insert5 insert5.c
execute time: 2 second(s)
现在c和java至少一样的速度了,貌似比较和谐。。。
【问题出现】以上测试系统配置请参见原帖(http://bbs.pgsqldb.com/index.php?t=msg&th=11114&start=0&rid=&S=056054310e9f2f0b432592d39810ffd4),且c和java都和数据库在同一机器上(192.168.11.58),但是将c和java程序同时移植到另外一台机器(192.168.2.50)上时,出事了,c程序要执行20miao,而java只跑了4秒钟。

难道在这里真的会出现c比java慢,而且慢很多的的情况吗?请各位牛人再次伸出援助之手,谢谢!

[更新: 三, 18 六月 2008 20:56]
[向版主推荐这条信息]

laser
Brigadier General



Administrator

发贴数: 7020
地区: tired for BJ
注册时间:
一月 2003
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 三, 18 六月 2008 17:29

两台机器一样?能说说移植方法么?编译?

[向版主推荐这条信息]

seth.Yang
Private First Class



发贴数: 10
注册时间:
五月 2008
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 三, 18 六月 2008 20:52

就是把源码copy的另外一台机器上重新编译,运行。2台机器的系统一致
SYS: rhel5_x64
GCC: x86_64-redhat-linux 4.1.1 20070105 (Red Hat 4.1.1-52)
JVM: Java HotSpot(TM) 64-Bit Server VM (build 1.5.0_14-b03, mixed mode)
就是CPU和内存第二台机器差点,普通的64位机器。
但java的性能下降不明显,而c明显变慢
[向版主推荐这条信息]

seth.Yang
Private First Class



发贴数: 10
注册时间:
五月 2008
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 五, 20 六月 2008 10:41

请各位牛人帮帮忙啊。
我怀疑问题是处在C调用接口上,C不可能比java慢的;如果能找到和java那套接口相匹配的C接口,问题应该就解决了。
[向版主推荐这条信息]

laser
Brigadier General



Administrator

发贴数: 7020
地区: tired for BJ
注册时间:
一月 2003
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 五, 20 六月 2008 11:53

有些怀疑是 cache的问题。
不过你是否能用 time 你的程序跑跑看,
看看是系统调用开销多还是USER开销多?
[向版主推荐这条信息]

孤独的猫
Corporal



发贴数: 24
注册时间:
四月 2008
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 五, 20 六月 2008 13:24

在LIBPG中用COPY命令来操作数据的插入试试看。

在我的机器上,用COPY命令比PERPARE后的INSERT要快很多
[向版主推荐这条信息]

soyo
First Sergeant



发贴数: 85
注册时间:
三月 2008
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 五, 20 六月 2008 15:58

另外台机器虽然系统一致,但PG配置是否一致?检查下temp_buffers、max_prepared_transactions等设置看看。
每个连接会话能够使用的内存是受一定限制的,例如,默认下栈内存只有1M或2M,但循环20000次的事务需要的内存可能不足了。可以改成这样试试:

for i=0;i<100;i++
{
begin
for j=0;j<200;j++
insert
end
}
[向版主推荐这条信息]

seth.Yang
Private First Class



发贴数: 10
注册时间:
五月 2008
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 五, 20 六月 2008 20:39

laser 引用 2008-06-20 11:53

有些怀疑是 cache的问题。
不过你是否能用 time 你的程序跑跑看,
看看是系统调用开销多还是USER开销多?


# time ./insert5
execute time: 12 second(s)

real 0m12.594s
user 0m0.128s
sys 0m4.130s

to soyo:
不好意思,没明白你的意思。我也没有表达清楚我的步骤:
我所谓的移植是原来C/java程序及数据库都在 192.168.11.58,后来把C/java程序一到192.168.2.50上跑,但数据库还是连到192.168.11.58

to 孤独的猫:
我去试试用copy替代insert
[向版主推荐这条信息]

laser
Brigadier General



Administrator

发贴数: 7020
地区: tired for BJ
注册时间:
一月 2003
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 五, 20 六月 2008 21:02

seth.Yang 引用 2008-06-20 20:39


# time ./insert5
execute time: 12 second(s)

real 0m12.594s
user 0m0.128s
sys 0m4.130s




有趣,真正执行了12.59s,用户用了0.128s,系统开销是4.13s,那还有8s多干什么去了?:)



Quote:


我所谓的移植是原来C/java程序及数据库都在 192.168.11.58,后来把C/java程序一到192.168.2.50上跑,但数据库还是连到192.168.11.58





你一开始为什么不说?
[向版主推荐这条信息]

laser
Brigadier General



Administrator

发贴数: 7020
地区: tired for BJ
注册时间:
一月 2003
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 五, 20 六月 2008 21:04

char sql[] 改成 char *sql;
我怀疑 sql[] 是否会有末尾的空零存在。。。。
[向版主推荐这条信息]

soyo
First Sergeant



发贴数: 85
注册时间:
三月 2008
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 五, 20 六月 2008 22:36

我原以为你把数据库也弄到第二台机器了,所以建议你检查第二台机器的PG配置,并用双循环把原来一个事务分拆为100个事务执行来测试。

不过数据库没迁移的话恐怕得另找原因了。另外,C代码
PQexecPrepared (conn, stmtName, 2, values, NULL, NULL, 0);
值得传递参数长度和类型来改进。
[向版主推荐这条信息]

laser
Brigadier General



Administrator

发贴数: 7020
地区: tired for BJ
注册时间:
一月 2003
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 五, 20 六月 2008 23:59

real - user - system 有巨大的鸿沟,只能说明系统在干一些“不得不干”的事情。
蹦到我脑海里的有两个:

1,DNS lookup
2,disk fsync

带来两个问题:

1,你的 hostaddr 参数是怎么设置的?
2,你确信你的 C 程序和 JAVA 程序绝对保证是逻辑上一样的吗?尤其是最后的end那部分。



[向版主推荐这条信息]

seth.Yang
Private First Class



发贴数: 10
注册时间:
五月 2008
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 一, 23 六月 2008 12:32

laser 引用 2008-06-20 23:59

real - user - system 有巨大的鸿沟,只能说明系统在干一些“不得不干”的事情。
蹦到我脑海里的有两个:

1,DNS lookup
2,disk fsync

带来两个问题:

1,你的 hostaddr 参数是怎么设置的?
2,你确信你的 C 程序和 JAVA 程序绝对保证是逻辑上一样的吗?尤其是最后的end那部分。






to 1: 我是直接用IP的,不会有dns lookup的消耗
to 2: 我现在不敢保证C和JAVA的逻辑是否严格一致了;原因如下:
今天又认真分析/比对了一下C和JAVA程序,发现了这样一个数据:java之所以快,是因为PreparedStatement.addBatch ()和 PreparedStatement.executeBatch ();如果不是使用这2个接口,而仅仅是将autocommit复位的话,效率和C代码是一样的。


import org.postgresql.jdbc2.optional.PoolingDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;

/**
* Created by IntelliJ IDEA.
* User: seth_yang
* Date: 2008-5-10
* Time: 14:07:04
*/
public class Insert {
private static DataSource ds;

static {
PoolingDataSource pds = new PoolingDataSource ();
pds.setDatabaseName ("db_name");
pds.setUser ("db_user");
pds.setPassword ("******");
pds.setServerName ("192.168.11.58");
ds = pds;
}

public static void main (String[] args) throws Exception {
int method = 0;
if (args.length != 0) method = Integer.parseInt (args [0]);
switch (method) {
case 1: insert1 (); break;
case 2: insert2 (); break;
case 3: insert3 (); break;
default: insert (); break;
}
}
private static void insert () throws SQLException {
Connection conn = ds.getConnection ();
String sql = "INSERT INTO tb_test (id, name) VALUES (1, 'test')";
try {
long current = System.currentTimeMillis ();
Statement stmt = conn.createStatement ();
for (int i = 0; i < 20000; i ++) {
stmt.execute (sql);
}
System.out.println ("execute time " + (System.currentTimeMillis () - current) / 1000 + " second(s)");
} finally {
conn.close ();
}
}

private static void insert1 () throws SQLException {
Connection conn = ds.getConnection ();
conn.setAutoCommit (false);
String sql = "INSERT INTO tb_test (id, name) VALUES (1, 'test')";
try {
long current = System.currentTimeMillis ();
Statement stmt = conn.createStatement ();
for (int i = 0; i < 20000; i ++) {
stmt.execute (sql);
}
conn.commit ();
System.out.println ("execute time " + (System.currentTimeMillis () - current) / 1000 + " second(s)");
} catch (SQLException ex) {
conn.rollback ();
throw ex;
} finally {
conn.close ();
}
}

private static void insert2 () throws SQLException {
Connection conn = ds.getConnection ();
String sql = "INSERT INTO tb_test (id, name) VALUES (?, ?)";
try {
long current = System.currentTimeMillis ();
PreparedStatement pstmt = conn.prepareStatement (sql);
for (int i = 0; i < 20000; i ++) {
pstmt.setInt (1, 1);
pstmt.setString (2, "test");
pstmt.executeUpdate ();
}
System.out.println ("execute time " + (System.currentTimeMillis () - current) / 1000 + " second(s)");
} finally {
conn.close ();
}
}

private static void insert3 () throws SQLException {
Connection conn = ds.getConnection ();
String sql = "INSERT INTO tb_test (id, name) VALUES (?, ?)";
try {
long current = System.currentTimeMillis ();
PreparedStatement pstmt = conn.prepareStatement (sql);
for (int i = 0; i < 20000; i ++) {
pstmt.setInt (1, 1);
pstmt.setString (2, "test");
pstmt.addBatch ();
}
pstmt.executeBatch ();
System.out.println ("execute time " + (System.currentTimeMillis () - current) / 1000 + " second(s)");
} finally {
conn.close ();
}
}
}

分别执行insert1 和 insert 3 方法
[root@router50 tmp]# java -cp ../postgresql-8.2-504.jdbc3.jar Insert 1
execute time 18 second(s)

[root@router50 tmp]# java -cp ../postgresql-8.2-504.jdbc3.jar Insert 3
execute time 4 second(s)

执行C程序
./insert5
execute time: 13 second(s)
从上面的测试情况来看,我认为insert5.c中的逻辑应该是和 insert1 方法是一致的,但是和我以前一直测试的insert3方法有区别,区别应该就是addBatch.那么是不是有libpq版的addBatch呢?
[向版主推荐这条信息]

laser
Brigadier General



Administrator

发贴数: 7020
地区: tired for BJ
注册时间:
一月 2003
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 一, 23 六月 2008 14:25

一个SQL语句可以是分号分隔的N长的一个字串,类似这样的:
"insert into t values(..); insert into t values(..);insert into t values(..);"

你试试拼一个大串,然后执行一次看看?

当然,也可以一百个一执行。呵呵。
[向版主推荐这条信息]

阿弟
Brigadier General



发贴数: 5295
地区: pgsql天堂
注册时间:
八月 2003
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 一, 23 六月 2008 16:33

加上begin,end每句独立执行最快
[向版主推荐这条信息]

seth.Yang
Private First Class



发贴数: 10
注册时间:
五月 2008
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 二, 24 六月 2008 11:23

今天采用最笨的办法,把jdbc的驱动源码down下来跟了一把,发现java的execBatch其实做的事情就是把每一次addBatch的内容拼起来(当然还有一些必要的数据验证,及其他相关操作),和楼上各位说的差不多,就是拼成一句超大的INSERT语句,代码如下:


/* insert7.c */
#include
#include
#include
#include
#include "libpq-fe.h"

#define BLOCK_SIZE 1024
#define VALUE_SIZE 20

int main(int argc, char *argv[])
{
char *sql = NULL;
char *tmp = NULL;
char a[] = ", ";
PGconn *conn;
int i;
PGresult *ret;
time_t tp1, tp2;
char *msg;
int blocks = 1;

conn = PQconnectdb ("hostaddr=192.168.11.58 port=5432 dbname=db_name user=db_user password=******");
time (&tp1);
ret = PQexec (conn, "BEGIN");
msg = PQresultErrorMessage (ret);
PQclear (ret);
if (strlen (msg) > 0)
{
printf ("transaction begin faile caused by %s\n", msg);
PQfinish (conn);
return -1;
}
sql = (char *) malloc (BLOCK_SIZE);
sprintf (sql, "INSERT INTO tb_test VALUES ");
for (i = 0; i < 20000; i ++)
{
if (strlen (sql) >= blocks * BLOCK_SIZE - VALUE_SIZE)
{
/* 判断 INSER 语句是否超长,若超,重新分配 strlen(sql) + BLOCK_SIZE的空间 */
blocks ++;
tmp = (char *) malloc (blocks * BLOCK_SIZE);
memcpy (tmp, sql, strlen (sql));
free (sql);
sql = tmp;
}
tmp = malloc (VALUE_SIZE);
/* 拼 sql 语句 */
sprintf (tmp, "(%d, 'test%d')", i, i);
if (i > 0) strcat (sql, a);
strcat (sql, tmp);
free (tmp);
}

ret = PQexec (conn, sql);
msg = PQresultErrorMessage (ret);
PQclear (ret);
if (strlen (msg) > 0)
{
printf ("execute transaction fail caused by %s\n", msg);
PQexec (conn, "ROLLBACK");
}
else
{
PQexec (conn, "END");
}
time (&tp2);
printf ("execute time: %d second(s)\n", tp2 - tp1);
PQfinish (conn);
free (sql);
return 0;
}

$gcc -I/usr/local/postgres/8.2/include -L/usr/local/postgres/8.2/lib -lpq -o insert7 insert7.c
$export LD_LIBRARY_PATH=/usr/local/postgres/8.2/lib
$./insert7
execute time 2 second(s)

非常和谐,把java拉开了 2 秒的差距

多谢楼上各位耐心,热心的帮助!

[更新: 二, 24 六月 2008 11:26]
[向版主推荐这条信息]

laser
Brigadier General



Administrator

发贴数: 7020
地区: tired for BJ
注册时间:
一月 2003
回复:请教关于libpq的使用,内附测试代码及测试结果 【续】 二, 24 六月 2008 15:12

congra!



[向版主推荐这条信息]


下一个主题: postgresql-8.3.2的JDBC之连接?
上一个主题: 请教
转至论坛:

-=] 返回顶部 [=-

当前时间: 一 6月 30 16:23:56 CST 2008

生成本页面共花费时间: 0.063581943512 秒。
.:: 联系 :: 主页 ::.

Powered by: laserForum 0.56, PostgreSQL
Copyright ©2003 bitbird


总数:0 页次:1/0 首页 尾页  
总数:0 页次:1/0 首页 尾页  


所在合集/目录



发表评论:
文本/html模式切换 插入图片 文本/html模式切换


附件:



NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.

Copyright © 2005-2020 clq, All Rights Reserved
版权所有
桂ICP备15002303号-1