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