需要一种快速的方法来将大量的double转换为string

时间:2018-12-28 07:08:08

标签: c postgresql hpc

我正在编写一个用于高速计算程序的结果输出模块。

我的计划是:

  1. 我的任务是以相对较快的速度将结果插入数据库(PostgreSQL)。
  2. 我使用libpq的[COPY FROM STDIN],有人告诉我这是最快的方法。
  3. 该方法需要将结果转换为char *格式。

结果看起来像这样:

  1. 接下来的106年的每月现金流(总计为1272倍)。
  2. 每个条目大约有14个现金流。
  3. 大约2800个实体(用于测试数据的实体为2790个)。

数据库中的表如下所示:

  1. 表的每一行包含一个实体。
  2. 有一些前缀可以标识不同的实体。
  3. 现金流是在前缀(PGSQL中为float8 []类型)之后的双精度数组。

以下显示了在数据库中创建表的代码:

create table AgentCF(
PlanID     int4,
Agent      int4,
Senario    int4,
RM_Prev    float8[], DrvFac_Cur float8[], Prem       float8[],
Comm       float8[], CommOR     float8[], FixExp     float8[],
VarExp     float8[], CIRCFee    float8[], SaftyFund  float8[],
Surr       float8[], Benefit_1  float8[], Benefit_2  float8[],
Benefit_3  float8[], Benefit_4  float8[], Benefit_5  float8[],
Benefit_6  float8[], Benefit_7  float8[], Benefit_8  float8[],
Benefit_9  float8[], Benefit_10 float8[]
);

用于准备插入的CashFlow的功能的呈现代码:

void AsmbCF(char *buffer, int size, int ProdNo, int i, int Pos, int LineEnd)
{
    int     j, Step = sizeof(nodecf) / sizeof(double), PosST, Temp;
    double *LoopRate = &AllHeap[ProdNo].Heap.AgentRes[i].CF.NodeCF[0].Prem;
    strcpy_s(buffer, size, "{");
    for (j = 0; j < TOTLEN / 10; j++) {
        PosST = j * 10 * Step + Pos;
        sprintf_s(&buffer[strlen(buffer)], size - strlen(buffer), "%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,",
            LoopRate[PosST],
            LoopRate[PosST + 1 * Step],
            LoopRate[PosST + 2 * Step],
            LoopRate[PosST + 3 * Step],
            LoopRate[PosST + 4 * Step],
            LoopRate[PosST + 5 * Step],
            LoopRate[PosST + 6 * Step],
            LoopRate[PosST + 7 * Step],
            LoopRate[PosST + 8 * Step],
            LoopRate[PosST + 9 * Step]
        );
    }
    Temp = j * 10;
    PosST = Temp * Step + Pos;
    sprintf_s(&buffer[strlen(buffer)], size - strlen(buffer), "%f", LoopRate[PosST]);
    Temp = Temp + 1;
    for (j = Temp; j < TOTLEN; j++) {
        PosST = j * Step + Pos;
        sprintf_s(&buffer[strlen(buffer)], size - strlen(buffer), ",%f", LoopRate[PosST]);
    }
    if (LineEnd) {
        strcat_s(buffer, size, "}\n");
    }
    else {
        strcat_s(buffer, size, "}\t");
    }
}

以下是用于速度测试的代码:

void ThreadOutP(LPVOID pM)
{
    char       *buffer = malloc(BUFFLEN), sql[SQLLEN];
    int         Status, ProdNo = (int)pM, i, j, ben;
    PGconn     *conn = NULL;
    PGresult   *res;
    clock_t     begin, end;

    fprintf_s(fpOutP, "PlanID %d Start inseting...\n", AllHeap[ProdNo].PlanID);
    begin = clock();
    DBConn(&conn, CONNSTR, fpOutP);

#pragma region General cashflow
    //============================== Data Query ==============================
    //strcpy_s(&sql[0], SQLLEN, "COPY AgentCF(PlanID,Agent,Senario,Prem,Comm,CommOR,CIRCFee,SaftyFund,FixExp,VarExp,Surr");
    //for (ben = 1; ben <= AllHeap[ProdNo].Heap.TotNo.NoBenft; ben++) {
    //  strcat_s(&sql[0], SQLLEN, ",Benefit_");
    //  _itoa_s(ben, &sql[strlen(sql)], sizeof(sql) - strlen(sql), 10);
    //}
    //strcat_s(&sql[0], SQLLEN, ") FROM STDIN;");
    //res = PQexec(conn, &sql[0]);
    //if (PQresultStatus(res) != PGRES_COPY_IN) {
    //  fprintf_s(fpOutP, "Not in COPY_IN mode\n");
    //}
    //PQclear(res);
    //============================== Data Apply ==============================
    for (i = 0; i < AllHeap[ProdNo].MaxAgntPos + AllHeap[ProdNo].Heap.TotNo.NoSensi; i++) {
        sprintf_s(buffer, BUFFLEN, "%d\t%d\t%d\t", AllHeap[ProdNo].PlanID, AllHeap[ProdNo].Heap.AgentRes[i].Agent, AllHeap[ProdNo].Heap.AgentRes[i].Sensi);
        //Status = PQputCopyData(conn, buffer, (int)strlen(buffer));
        //if (1 != Status) {
        //  fprintf_s(fpOutP, "PlanID %d inserting error for agent %d\n", AllHeap[ProdNo].PlanID, AllHeap[ProdNo].Heap.AgentRes[i].Agent);
        //}
        for (j = 0; j < 8 + AllHeap[ProdNo].Heap.TotNo.NoBenft; j++) {
            if (j == 7 + AllHeap[ProdNo].Heap.TotNo.NoBenft) {
                AsmbCF(buffer, BUFFLEN, ProdNo, i, j, 1);
            }
            else {
                AsmbCF(buffer, BUFFLEN, ProdNo, i, j, 0);
            }
            //Status = PQputCopyData(conn, buffer, (int)strlen(buffer));
            //if (1 != Status) {
            //  fprintf_s(fpOutP, "PlanID %d inserting error for agent %d\n", AllHeap[ProdNo].PlanID, AllHeap[ProdNo].Heap.AgentRes[i].Agent);
            //}
        }
    }
    //Status = PQputCopyEnd(conn, NULL);
#pragma endregion

#pragma region K cashflow

#pragma endregion

    PQfinish(conn);
    FreeProd(ProdNo);
    free(buffer);
    end = clock();
    fprintf_s(fpOutP, "PlanID %d inserted, total %d rows inserted, %d millisecond cost\n", AllHeap[ProdNo].PlanID, i, end - begin);
    AllHeap[ProdNo].Printed = 1;
}

请注意,我禁用了涉及插入的代码。

测试结果为:

  1. 仅组装字符串的成本为45930毫秒。
  2. 组装字符串和插入件的成本为54829毫秒。

因此,大部分成本在于将double转换为char。

因此,我想问一下是否有一种更快的将double系列转换为字符串的方法,因为与计算成本相比,瓶颈实际上是结果的输出。

顺便说一下,我的平台是Windows 10,PostgreSQL 11,Visual Studio 2017。

非常感谢!

4 个答案:

答案 0 :(得分:2)

实际上有几种更快的方法可以将浮点数准确地表示为字符串,其中之一是Grisu, by Florian Loitsch

This github repo在C和C ++中比较了几种算法,其中包含source code for the Grisu2 method in C,他声称比sprintf快5.7倍。

但是,同一仓库(Milo Yip)的作者提供了他自己的C ++单头实现,据称它快9.1倍,这可能是因为更多的功能已完全内联。我认为将此代码移植到C应该很简单,因为它不使用任何特殊的C ++语法。

答案 1 :(得分:1)

  

将大量double转换为字符串的快速方法

对于完整的double应用范围,请使用sprintf(buf, "%a", some_double)。如果需要十进制输出,请使用"%e"

任何其他代码只有包含准确性或允许的输入范围 ,它才会更快。

常规方法是将double x转换为某个较大的整数并将其转换为字符串。这意味着OP尚未明确表达对x的限制。

即使某些其他方法出现得更快,也可能不会随着代码的发展或移植而变得更快。


OP需要发布的是用于客观性能评估的速度测试代码。

答案 2 :(得分:1)

我对原始代码做了一些记账:


  Total score("function" calls):
    2 + 4*TOTLEN * strlen()
    1 + 2*TOTLEN * sprintf() 
    1 * strcat()

  Estimated string() cost:
    3 + 4* size * (TOTLEN*TOTLEN) / 2 (measured in characters)

  Estimated sprintf() cost:
    2 * TOTLEN (measured in %lf conversions)
    2 * size (measured in characters)

现在,我不知道TOTLEN是什么,但是在不断增长的字符串上调用strlen()和朋友会导致二次行为,请参见https://en.wikipedia.org/wiki/Joel_Spolsky#Schlemiel_the_Painter.27s_algorithm


  • 在优化之前进行个人资料/测量(或思考)
  • snprintf()正确使用时是溢出安全的;阅读手册页并使用返回值
  • strxxx_x()函数几乎没有用,它们仅是为了使PHB满意

答案 3 :(得分:0)

替代chux的答案,我做了以下功能:

__inline char* dbltoa(char* buff, double A, int Precision)
{
    int     Temp;
    char   *ptr;

    Temp = (int)A;
    _itoa_s(Temp, buff, 50, 10);
    ptr = buff + strlen(buff);
    ptr[0] = '.';
    Temp = (int)((A - Temp) * pow(10, Precision));
    _itoa_s(Temp, ptr + 1, 50, 10);
    return ptr + strlen(ptr);
}

并更新了创建CashFlow字符串的函数:

void AsmbCF(char *buffer, int size, int ProdNo, int i, int Pos, int LineEnd)
{
    int     j, Step = sizeof(nodecf) / sizeof(double), PosST, Temp;
    double *LoopRate = &AllHeap[ProdNo].Heap.AgentRes[i].CF.NodeCF[0].Prem;
    char   *ptr;
    strcpy_s(buffer, size, "{");
    ptr = buffer + 1;
    for (j = 0; j < TOTLEN; j++) {
        PosST = j * Step + Pos;
        ptr = dbltoa(ptr, LoopRate[PosST], 8);
        ptr[0] = ',';
        ptr++;
    }
    ptr[-1] = 0;
    if (LineEnd) {
        strcat_s(buffer, size, "}\n");
    }
    else {
        strcat_s(buffer, size, "}\t");
    }
}

不带插入的测试结果为4558毫秒,而带插入的测试结果为29260毫秒(数据库的并行运行可能使比率不相等)。