我正在编写一个用于高速计算程序的结果输出模块。
我的计划是:
结果看起来像这样:
数据库中的表如下所示:
以下显示了在数据库中创建表的代码:
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;
}
请注意,我禁用了涉及插入的代码。
测试结果为:
因此,大部分成本在于将double转换为char。
因此,我想问一下是否有一种更快的将double系列转换为字符串的方法,因为与计算成本相比,瓶颈实际上是结果的输出。
顺便说一下,我的平台是Windows 10,PostgreSQL 11,Visual Studio 2017。
非常感谢!
答案 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毫秒(数据库的并行运行可能使比率不相等)。