fread读入优化,寻找速度极限

本文对比了C++中getchar()与fread()在不同数据规模下的读取效率,通过对1e5到1e7的数据多次测试,验证了fread()在文件读取上的优势,可达约9倍优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

序:
在之前的测试中,我们比较了四种读入方式,发现使用读入优化是最快的选择,但是我们知道fread()是比它更快的方法。这一次,我们对比四种读入优化,探寻C++读取速度的极限
分别是getchar()两种方式以及fread()两种方式。

测试数据为1e5,1e6,1e7的大小,每次测试循环5次或7次力求测试结果的稳定性。(共测试6次)


首先是两种getchar()读入,由于在之前的测试中出现过,故只附代码。

inline void read1(int &curr)
{
    static char c;
    c = getchar();
    while(c < '0' || c > '9')   c = getchar();
    while(c >= '0' && c <= '9')
    {
        curr = curr*10+c-'0';
        c = getchar();
    }
    return ;
}

inline void read2()
{
    static char c;
    input = 0;
    c = getchar();
    while(c < '0' || c > '9')   c = getchar();
    while(c >= '0' && c <= '9')
    {
        input = input*10+c-'0';
        c = getchar();
    }
    return ;
}

void Init1()//传地址
{
    freopen("test.in", "r", stdin);
    int n = 0, i;
    int startTime = clock();
    read1(n); 
    for(unsigned i = 0; i != n; ++i)
    {
        read1(m[i]);
    }
    int endTime = clock();
    ans[1][k] += (double)(endTime-startTime)/CLOCKS_PER_SEC;
    fclose(stdin);
    return ;
}

void Init2()//利用全局变量
{
    freopen("test.in", "r", stdin);
    int n = 0, i;
    int startTime = clock();
    read2();
    n = input; 
    for(unsigned i = 0; i != n; ++i)
    {
        read2();
        m[i] = input;
    }
    int endTime = clock();
    ans[2][k] += (double)(endTime-startTime)/CLOCKS_PER_SEC;
    fclose(stdin);
    return ;
}

重点是fread在windows下的使用
由于windows环境中的换行符是’\r”\n’所以直接读取int是不现实的(总之并没有成功,全是乱码)。
于是我们采取读字符的方式读取。

首先是fread函数的参数:fread(char *pos, int len_per, int num, FILE *fp)。
即读取的地址,每个部分的长度(可以是结构体,长度用sizeof(…)), 个数,文件指针

那么对于我们这种情况,且文件是使用freopen(“test.in”, “r”, stdin)打开的来说,
就是这样的:fread(char* pos, 1, int num, stdin);

对于字符串(基于目前状况)而言,我们有三种方式计算长度,
一是根据数据范围将长度置于足够大(小心炸空间),这样最快,一次便能读取全;
二是通过C语言的feek和ftell函数遍历整个文件计算出确切大小
三是设定一个大小,多次读取

那么一的代码如下:

void Get_All()
{
    long long file_lenth = maxn*10;//足够大
    fread(Buffer,1, file_lenth, stdin);
    X = Buffer;//当前指针
    return ;
}

二是这样的:

void Get_All()
{
    long long file_lenth;
    fseek(stdin, 0, SEEK_END);//从文件头遍历到文件尾
    file_lenth = ftell(stdin);//文件字节数大小
    rewind(stdin);
    Buffer = (char*)malloc(1*file_lenth);//分配空间
    fread(Buffer,1, file_lenth, stdin);
    X = Buffer;//当前指针
    return ;
}

三也类似:

inline char Get_Char()  
{  
    if(S==T)//T是尾指针,S == T用于判断是否到尾
    {  
        T=(S=buffer)+fread(buffer,1,maxn,stdin);//maxn预先设定好
        if(S==T) return EOF;  
    }  
    return *S++;  //由于是多次读取,故在从中拆除整数时使用
}
int Get_Int()
{
    char c;  
    int re=0;  
    for(c=Get_Char();c<'0'||c>'9';c=Get_Char());  
    while(c>='0'&&c<='9')  
           re=(re<<1)+(re<<3)+(c-'0'),c=Get_Char();  
    return re;  
}

一二用于得到整数的函数更为简单,不需要判断字符串是否到尾,因为它读取了整个文件

int Get_Num()
{
    c = *X;
    re = 0;
    while(c < '0' || c > '9')   c = *++X;
    while(c >= '0' && c <= '9')
    {
        re = re*10+c-'0';
        c = *++X;
    }
    return re;
}

那么准备工作就完成了,下面开始测试

for(unsigned j = 0; j != 5; ++j)
    {
        for(unsigned i = 0; i != 7; ++i)//多次循环
        {
            k = j;
            Init1();    //getchar()-1
            Init2();//getchar()-2
            Init3();//fread()-3
            Init4();//fread()-1
            Init5();//scanf()
        }
    }

利用time.h的函数:

int StartTime = clock();
...
int EndTime = clock();
ans[curr][k] += (double)(endTime-startTime)/CLOCKS_PER_SEC;//计算总时长

测试结果如下:

1e7*5 *5

2.961 2.874 2.888 2.914 2.854

2.733 2.764 2.793 2.722 2.733

2.493 2.440 2.446 2.464 2.479

2.113 2.134 2.210 2.135 2.157

18.586 18.552 18.613 18.493 18.529

14.491000 13.745000 12.322000 10.749000 92.773000

1e7*7 *5

4.030 4.018 4.013 4.069 4.723

3.797 3.855 3.945 3.932 4.544

3.366 3.516 3.474 3.476 3.889

3.040 3.005 3.037 3.019 3.390

25.993 25.679 26.097 26.229 30.518

20.853000 20.073000 17.721000 15.491000 134.516000

1e6*5 *5

0.301 0.277 0.303 0.271 0.284

0.284 0.274 0.308 0.267 0.285

0.252 0.237 0.251 0.236 0.242

0.219 0.204 0.219 0.208 0.210

1.872 1.840 1.925 1.802 1.861

1.436000 1.418000 1.218000 1.060000 9.300000

1e6*7 *5

0.443 0.386 0.404 0.395 0.407

0.389 0.390 0.387 0.386 0.391

0.342 0.340 0.347 0.334 0.347

0.287 0.291 0.301 0.294 0.301

2.548 2.549 2.612 2.538 2.598

2.035000 1.943000 1.710000 1.474000 12.845000

1e5*5 *5

0.044 0.038 0.041 0.040 0.040

0.044 0.038 0.040 0.038 0.037

0.033 0.033 0.034 0.031 0.033

0.030 0.030 0.027 0.028 0.030

0.280 0.271 0.258 0.264 0.255

0.203000 0.197000 0.164000 0.145000 1.328000

1e5*7 *5

0.054 0.039 0.042 0.042 0.040 0.000 0.000

0.047 0.038 0.035 0.039 0.038 0.000 0.000

0.038 0.036 0.031 0.035 0.032 0.000 0.000

0.034 0.030 0.030 0.028 0.030 0.000 0.000

0.307 0.271 0.259 0.260 0.263 0.000 0.000

0.217000 0.197000 0.172000 0.152000 1.360000

结果十分稳定:T5 >> T1 > T2 > T3 > T4。
经过计算,getchar相对于scanf()的速度有大概6-7倍的优化,而使用fread()则可以达到约9倍的优化。


那么,对于fread(二)呐?
起初我对它不抱有信心:遍历文件需要时间,我把长度规定好肯定比你快啊
事实证明差不多,速度略慢于fread(一)。
测试1e7*5 *7,三种fread对比如下
19.067000 16.611000 17.084000


完整测试代码如下:

/*
About: read-data-final
Auther: kongse_qi
date: 2017/04/17
result: Get_Num is nearly 10 times faster than scanf();
*/

#include <bits/stdc++.h>
#define maxn 10000050
using namespace std;

int input, k;
int m[maxn];
double ans[10][20];

inline void read1(int &curr)
{
    static char c;
    c = getchar();
    while(c < '0' || c > '9')   c = getchar();
    while(c >= '0' && c <= '9')
    {
        curr = curr*10+c-'0';
        c = getchar();
    }
    return ;
}

inline void read2()
{
    static char c;
    input = 0;
    c = getchar();
    while(c < '0' || c > '9')   c = getchar();
    while(c >= '0' && c <= '9')
    {
        input = input*10+c-'0';
        c = getchar();
    }
    return ;
}

void Init1()//传地址
{
    freopen("test.in", "r", stdin);
    int n = 0, i;
    int startTime = clock();
    read1(n); 
    for(unsigned i = 0; i != n; ++i)
    {
        read1(m[i]);
    }
    int endTime = clock();
    ans[1][k] += (double)(endTime-startTime)/CLOCKS_PER_SEC;
    fclose(stdin);
    return ;
}

void Init2()//利用全局变量
{
    freopen("test.in", "r", stdin);
    int n = 0, i;
    int startTime = clock();
    read2();
    n = input; 
    for(unsigned i = 0; i != n; ++i)
    {
        read2();
        m[i] = input;
    }
    int endTime = clock();
    ans[2][k] += (double)(endTime-startTime)/CLOCKS_PER_SEC;
    fclose(stdin);
    return ;
}

char buffer[maxn],*S,*T; 
inline char Get_Char()  
{  
    if(S==T)  
    {  
        T=(S=buffer)+fread(buffer,1,maxn,stdin);
        if(S==T) return EOF;  
    }  
    return *S++;  
}

int Get_Int()
{
    char c;  
    int re=0;  
    for(c=Get_Char();c<'0'||c>'9';c=Get_Char());  
    while(c>='0'&&c<='9')  
           re=(re<<1)+(re<<3)+(c-'0'),c=Get_Char();  
    return re;  
}

void Init3()
{
    freopen("test.in", "r", stdin);
    int n = 0, i;
    int startTime = clock();
    n = Get_Int(); 
    for(unsigned i = 0; i != n; ++i)
    {
        m[i] = Get_Int(); 
    }
    int endTime = clock();
    ans[3][k] += (double)(endTime-startTime)/CLOCKS_PER_SEC;
    fclose(stdin);
    return ;
}

char *X, Buffer[maxn*10], c, *buffer_;
int re;

void Get_All()
{
    long long file_lenth = maxn*10;//int占8,还有换行等
    fread(Buffer,1, file_lenth, stdin);
    X = Buffer;
    return ;
}

void Get_All1()
{
    long long file_lenth;
    fseek(stdin, 0, SEEK_END);
    file_lenth = ftell(stdin);
    rewind(stdin);
    buffer_ = (char*)malloc(1*file_lenth);
    fread(buffer_,1, file_lenth, stdin);
    X = buffer;
    return ;
}

int Get_Num()
{
    c = *X;
    re = 0;
    while(c < '0' || c > '9')   c = *++X;
    while(c >= '0' && c <= '9')
    {
        re = re*10+c-'0';
        c = *++X;
    }
    return re;
}

void Init4()
{
    freopen("test.in", "r", stdin);
    int n = 0, i;
    int startTime = clock();
    Get_All();
    n = Get_Num(); 
    for(unsigned i = 0; i != n; ++i)
    {
        m[i] = Get_Num(); 
    }
    int endTime = clock();
    ans[4][k] += (double)(endTime-startTime)/CLOCKS_PER_SEC;
    fclose(stdin);
    return ;
}

void Init5()
{
    freopen("test.in", "r", stdin);
    int n = 0, i;
    int startTime = clock();
    scanf("%d", &n);
    for(unsigned i = 0; i != n; ++i)
    {
        scanf("%d", &m[i]); 
    }
    int endTime = clock();
    ans[5][k] += (double)(endTime-startTime)/CLOCKS_PER_SEC;
    fclose(stdin);
    return ;
}

void Init6()
{
    freopen("test.in", "r", stdin);
    int n = 0, i;
    int startTime = clock();
    Get_All1();
    n = Get_Num(); 
    for(unsigned i = 0; i != n; ++i)
    {
        m[i] = Get_Num(); 
    }
    int endTime = clock();
    ans[6][k] += (double)(endTime-startTime)/CLOCKS_PER_SEC;
    free(buffer_);
    fclose(stdin);
    return ;
}

int main()
{
    int i, j;
    double tot[10] = {0};

    for(unsigned j = 0; j != 5; ++j)
    {
        k = j;
        for(unsigned i = 0; i != 7; ++i)
        {
            Init1();    
            Init2();
            Init3();
            Init4();
            Init5();
            Init6();
        }
    }
    for(unsigned j = 1; j != 7; ++j)
    {
        for(unsigned i = 0; i != 5; ++i)
        {
            printf("%.3f ", ans[j][i]); 
            tot[j] += ans[j][i];
        }
        printf("\n");
    }

    for(unsigned i = 1; i != 7; ++i)
    {
        printf("%lf ", tot[i]);
    }

    return 0;
}

再次证明其是正确的,将其读入的数输出文件test2.out与读入文件test.in进行比较。

fc test.in test1.out
正在比较文件 test.in 和 TEST1.OUT
FC: 找不到差异

尾:
温馨提示:使用之前也需要计算好空间,不要为了省读入的时间而导致MLE。

自此测试结束。
箜瑟_qi 2017.04.17 20:38

### 使用 `fread` 函数读取二维数组 为了使用 `fread` 函数从文件中读取二维数组,在C语言中有几个关键点需要注意。首先,确保文件是以二进制模式打开的,因为 `fread` 主要用于读取二进制数据[^3]。 下面是一个具体的例子来展示如何利用 `fread` 将文件中的数据加载到二维数组中: 假设有一个简单的文本文件或二进制文件,其中的数据按照特定顺序排列,可以直接映射成一个 m 行 n 列的矩阵形式。这里提供一段示范性的代码片段用来说明这一过程: ```c #include <stdio.h> #include <stdlib.h> #define ROWS 5 /* 定义行数 */ #define COLS 10 /* 定义列数 */ int main() { FILE *file; int matrix[ROWS][COLS]; // 创建一个二维数组 file = fopen("data.bin", "rb"); // 打开二进制文件进行只读操作 if (file == NULL){ perror("Error opening file"); return(-1); } size_t result = fread(matrix, sizeof(int), ROWS*COLS, file); // 一次性读取整个矩阵的内容 if(result != ROWS*COLS) { printf("Reading error\n"); fclose(file); return (-2); } fclose(file); // 输出读取的结果验证是否成功 for (size_t i=0; i<ROWS; ++i){ for(size_t j=0;j<COLS;++j){ printf("%d ",matrix[i][j]); } putchar('\n'); } return(0); } ``` 这段程序先定义了一个固定大小的二维整型数组作为目标容器,接着尝试以二进制读写的方式打开名为 `"data.bin"` 的外部文件,并通过调用 `fread()` 来填充这个预先分配好的空间。最后关闭文件句柄并打印出所得到的数据以便确认一切正常工作。 值得注意的是,当处理像 BMP 这样的图形文件时,由于其特殊的结构(比如每行像素数量需满足 dword 对齐),实际应用中还需要额外考虑去除可能存在的填充字节等问题[^2]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值