我毫不怀疑这个地方有一个答案,我找不到它。
经过长时间的休息后,我刚回到c,非常生疏,所以请原谅我的愚蠢错误。我需要生成一个大的(可能等于10mb)字符串。我不知道它会建成多长时间。
我尝试了以下两种测试速度的方法:
int main() {
#if 1
size_t message_len = 1; /* + 1 for terminating NULL */
char *buffer = (char*) malloc(message_len);
for (int i = 0; i < 200000; i++)
{
int size = snprintf(NULL, 0, "%d \n", i);
char * a = malloc(size + 1);
sprintf(a, "%d \n", i);
message_len += 1 + strlen(a); /* 1 + for separator ';' */
buffer = (char*) realloc(buffer, message_len);
strncat(buffer, a, message_len);
}
#else
FILE *f = fopen("test", "w");
if (f == NULL) return -1;
for (int i = 0; i < 200000; i++)
{
fprintf(f, "%d \n", i);
}
fclose(f);
FILE *fp = fopen("test", "r");
fseek(fp, 0, SEEK_END);
long fsize = ftell(f);
fseek(fp, 0, SEEK_SET);
char *buffer = malloc(fsize + 1);
fread(buffer, fsize, 1, f);
fclose(fp);
buffer[fsize] = 0;
#endif
char substr[56];
memcpy(substr, buffer, 56);
printf("%s", substr);
return 1;
}
每次连接字符串的第一个解决方案需要3.8秒,写入文件的第二个解决方案需要0.02秒。
当然有一种快速的方法可以在c中构建一个大字符串而不需要读取和写入文件?我只是做一些非常低效的事情吗?如果没有,我可以写入某种文件对象,然后在最后阅读它,从不保存它?
在C#中你会使用字符串缓冲来避免缓慢的连接,c中的等值是什么?
提前致谢。
答案 0 :(得分:4)
你用这些线条让生活变得非常粗糙:
for (int i = 0; i < 200000; i++)
{
int size = snprintf(NULL, 0, "%d \n", i); // << executed in first loop only
char * a = malloc(size + 1); // allocate enough space for "0 \n" + 1
sprintf(a, "%d \n", i); // may try to squeeze "199999 \n" into a
message_len += 1 + strlen(a); /* 1 + for separator ';' */
buffer = (char*) realloc(buffer, message_len);
strncat(buffer, a, message_len);
}
你计算size
并在第一次迭代中为a
分配空间 - 然后继续在每次后续迭代中使用它(i
变大,你原则上会超过分配给a
的存储空间。如果你做得正确(在每个循环中为a
分配大小),你也必须在每个循环中free
,或者造成巨大的内存泄漏。
C中的解决方案是预先分配大量内存 - 并且仅在紧急情况下重新分配。如果您“大致”知道字符串的大小,请立即分配所有内存;跟踪它有多大,如果你做空,还要增加更多。最后,你总能“回馈你没用过的东西”。对realloc
的调用过多会导致内存不断移动(因为您经常没有足够的连续内存)。正如@Matt在他的评论中澄清的那样:每次调用realloc
都会移动整个内存块存在真正的风险 - 随着块变大,这会成为一个二次增加的负载系统。这里是一个可能更好的解决方案(完整,有小N和BLOCK测试只是为了显示原理,你会希望使用大N(您的200000值),较大块 - 和摆脱printf
声明那些表明事情正在发挥作用):
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define N 2000000
#define BLOCK 32
int main(void) {
size_t message_len = BLOCK; //
char *buffer = (char*) malloc(message_len);
int bb;
int i, n=0;
char* a = buffer;
clock_t start, stop;
for(bb = 1; bb < 128; bb *= 2) {
int rCount = 0;
start = clock();
for (i = 0; i < N; i++)
{
a = buffer + n;
n += sprintf(a, "%d \n", i);
if ((message_len - n) < BLOCK*bb) {
rCount++;
message_len += BLOCK*bb;
//printf("increasing buffer\n");
//printf("increased buffer to %ld\n", (long int)message_len);
buffer = realloc(buffer, message_len);
}
}
stop = clock();
printf("\nat the end, buffer length is %d; rCount = %d\n", strlen(buffer), rCount);
// buffer = realloc(buffer, strlen(buffer+1));
//printf("buffer is now: \n%s\n", buffer);
printf("time taken with blocksize = %d: %.1f ms\n", BLOCK*bb, (stop - start) * 1000.0 / CLOCKS_PER_SEC);
}
}
您需要为BLOCK
使用相当大的值 - 这会将调用次数限制为realloc
。我会用100000这样的东西;无论如何,你最终摆脱了空间。
编辑我修改了我发布的代码以允许循环的时间 - 将N增加到200万以获得“合理的时间”。我还最小化了初始内存分配(迫使大量调用realloc
并修复了一个错误(当realloc
必须移动内存时,a
不再指向buffer
中的偏移量{1}}。现在通过跟踪n
中的字符串长度来解决这个问题。
这非常快 - 最小块为450 ms,较大块(200万个数字)为350 ms。这与您的文件读/写操作具有可比性(在我的测量分辨率范围内)。但是 - 文件I / O流和相关的内存管理都经过了高度优化......
答案 1 :(得分:1)
我遗漏了一些细节,但我的做法一般都是这样的
创建一个像这样的结构
typedef struct {
char *curr ;
char *start ;
char *end ;
} VBUF ;
沿着这些方向编写一些函数:
void vbuf_alloc(VBUF *v,int n)
{
v->start = malloc(n) ;
v->end = v->start + n ;
v->curr = v->start ;
}
int vbuf_add(VBUF *v,char *s,int length)
{
if (v->end - v->curr < length) {
vbuf_realloc(v,(v->end - v->start) * 2) ;
}
memcpy(v->curr,s,length) ;
v->curr += length ;
return length ;
}
int vbuf_adds(VBUF *v,char *s)
{
return vbud_add(v,s,strlen(s)) ;
}
您可以根据需要扩展这套功能。
答案 2 :(得分:0)
c
没有对象,所以没有等同于C#
字符串缓冲区(尽管在C++
中你会使用std::string
)。
通过不在每个追加上调用realloc,并且永远不会按照你的方式调用malloc,你将获得性能提升。
只需声明一个足够大的char []来打印最大的int,就可以完全避免使用malloc;这也可以避免使用snprintf,而且尺寸相当小。
而不是经常调用realloc,你应该将缓冲区增加一些合理的大小...比如4kb(一个不错的大小与页面大小相对应),并且只有当它接近于时才会再次增长它耗尽(也就是说,当它的当前用量小于你从上面使用的数组大小时)。
答案 3 :(得分:0)
我建议在每个连续的字符串上代替realloc
,如果长度太短,请提前智能realloc
。换句话说,尽可能避免重新分配。
伪代码中的天真实现可能类似于
Initialize an int/long to "written so far"
Initialize an int/long to remember "buffer size"
Alloc memory for a string up to the "buffer size"
Read in the next chunk into a temporary buffer
Get the "chunk size" from the temporary buffer
If "written so far" + "chunk size" > "buffer size"
Reallocate the chunk to be much bigger (double "buffer size"?)
Set the new "buffer size"
Copy the data from the temporary buffer to "buffer address" + "written so far" + 1
Set "written so far" to "written so far" + "chunk size"
我只是将它们放在一起,因此可能存在索引错误,但您明白了:只有在拥有时才分配和复制,而不是每次都通过循环。