【问题标题】:How does this strange I/O method work?这种奇怪的 I/O 方法是如何工作的?
【发布时间】:2017-06-04 13:03:01
【问题描述】:

在 C++ 中获取输入输出时,我只使用了 scanf/printf 和 cin/cout。现在我最近遇到了this 代码以一种奇怪的方式进行 I/O。

另外请注意,这种 I/O 方法会导致代码运行得非常快,因为此代码使用与大多数其他代码几乎相同的算法,但它的执行时间要短得多。为什么这个 I/O 如此之快?一般来说它是如何工作的?

编辑:代码

 #include <bits/stdtr1c++.h>

 #define MAXN 200010
 #define MAXQ 200010
 #define MAXV 1000010
 #define clr(ar) memset(ar, 0, sizeof(ar))
 #define read() freopen("lol.txt", "r", stdin)

 using namespace std;

 const int block_size = 633;

 long long res, out[MAXQ]; int n, q, ar[MAXN], val[MAXN], freq[MAXV];

 namespace fastio{
     int ptr, ye;
     char temp[25], str[8333667], out[8333669];

     void init(){
         ptr = 0, ye = 0;
         fread(str, 1, 8333667, stdin);
     }

     inline int number(){
         int i, j, val = 0;

         while (str[ptr] < 45 || str[ptr] > 57) ptr++;
         while (str[ptr] > 47 && str[ptr] < 58) val = (val * 10) + (str[ptr++] - 48);
         return val;
     }

     inline void convert(long long x){
         int i, d = 0;

         for (; ;){
             temp[++d] = (x % 10) + 48;
             x /= 10;
             if (!x) break;
         }
         for (i = d; i; i--) out[ye++] = temp[i];
         out[ye++] = 10;
     }

     inline void print(){
         fwrite(out, 1, ye, stdout);
     } }

 struct query{
     int l, r, d, i;

     inline query() {}
     inline query(int a, int b, int c){
         i = c;
         l = a, r = b, d = l / block_size;
     }

     inline bool operator < (const query& other) const{
         if (d != other.d) return (d < other.d);
         return ((d & 1) ? (r < other.r) : (r > other.r));
     } } Q[MAXQ];

 void compress(int n, int* in, int* out){
     unordered_map <int, int> mp;
     for (int i = 0; i < n; i++) out[i] = mp.emplace(in[i], mp.size()).first->second; }

 inline void insert(int i){
     res += (long long)val[i] * (1 + 2 * freq[ar[i]]++); }

 inline void erase(int i){
     res -= (long long)val[i] * (1 + 2 * --freq[ar[i]]); }

 inline void run(){
     sort(Q, Q + q);
     int i, l, r, a = 0, b = 0;

     for (res = 0, i = 0; i < q; i++){
         l = Q[i].l, r = Q[i].r;
         while (a > l) insert(--a);
         while (b <= r) insert(b++);
         while (a < l) erase(a++);
         while (b > (r + 1)) erase(--b);
         out[Q[i].i] = res;
     }
     for (i = 0; i < q; i++) fastio::convert(out[i]); }

 int main(){
     fastio::init();
     int n, i, j, k, a, b;

     n = fastio::number();
     q = fastio::number();
     for (i = 0; i < n; i++) val[i] = fastio::number();
     compress(n, val, ar);

     for (i = 0; i < q; i++){
         a = fastio::number();
         b = fastio::number();
         Q[i] = query(a - 1, b - 1, i);
     }

     run();
     fastio::print();
     return 0; }

【问题讨论】:

  • 请自行提出问题。也就是说,它不应该依赖于本网站之外的内容。在问题中发布相关代码。
  • 您是否在问为什么这个char str[8333667]; fread(str, 1, 8333667, stdin); 会比其他方法更快?请将该代码的相关部分添加到您的问题中。
  • 此代码使用特定于任务的解析,比 scanf 或 c++ iostream cin 更快,因为此代码仅解析许多可能的数字格式的一小部分,并且不检查输入是否有错误。此代码中的逻辑更少,分支更少,因此速度更快。并且只有 1 个系统调用可以一次读取完整文件,并将 fread 读取到内存中,没有很多系统调用和小 4KB 或类似经典 scanf/cin 的缓冲区。
  • @akka 检查错误?比如文件打不开怎么办?还是从文件中读取错误?或者如果解析内容时出错?需要检查和处理这些(以及更多)条件。

标签: c++ io


【解决方案1】:

此解决方案http://codeforces.com/contest/86/submission/22526466(624 毫秒,使用 32 MB RAM)使用单次读取和手动解析内存中的数字(因此它使用更多内存);许多其他解决方案速度较慢并使用scanf (http://codeforces.com/contest/86/submission/27561563 1620 ms 9MB) 或C++ iostream cin (http://codeforces.com/contest/86/submission/27558562 3118 ms, 15 MB)。并非所有解决方案的差异都来自输入输出和解析(解决方案方法也有差异),但有些是。

 fread(str, 1, 8333667, stdin);

此代码使用单个fread libcall 读取最多8MB,即完整文件。该文件最多可以有 2 (n,t) + 200000 (a_i) + 2*200000 (l,r) 6/7 位数字,带或不带换行符或由一个 (?) 空格分隔,因此大约 8 个字符数字的最大值(数字为 6 或 7,因为也允许 1000000,以及 1 个空格或\n);最大输入文件大小为 0.6 M * 8 字节 =~ 5 MB。

 inline int number(){
     int i, j, val = 0;

     while (str[ptr] < 45 || str[ptr] > 57) ptr++;
     while (str[ptr] > 47 && str[ptr] < 58) val = (val * 10) + (str[ptr++] - 48);
     return val;
 }

然后代码使用解析十进制整数的手动代码。根据ascii表,48...57的http://www.asciitable.com/十进制码是十进制数字(第二个while循环):'0'...'9',我们可以把字母码减去48得到数字;将部分读取 val 乘以 10 并添加当前数字。第一个 while 循环中的 chr&lt;45 || chr &gt; 57 听起来像是从输入中跳过非数字。这是不正确的,因为此代码不会解析代码 45、46、47 = '-', '.', '/',并且不会读取这些字符之后的任何数字。

 n = fastio::number();
 q = fastio::number();
 for (i = 0; i < n; i++) val[i] = fastio::number();

 for (i = 0; i < q; i++){
     a = fastio::number();
     b = fastio::number();

实际阅读使用这种fastio::number()方法;和其他解决方案在循环中使用scanf 或iostream operator &lt;&lt; 的调用:

for (int i = 0; i < N; i++) {
    scanf("%d", &(arr[i]));
    add(arr[i]);
}

for (int i = 1; i <= n; ++i)
    cin >> a[i];

这两种方法都更通用,但它们会调用库,这将从内部缓冲区(如 4KB)中读取一些字符或调用操作系统系统调用以重新填充缓冲区,并且每个函数都会进行多次检查并报告错误:对于每个数量的输入 scanf 将重新解析相同的 format string of first argument,并将执行 POSIX http://pubs.opengroup.org/onlinepubs/7908799/xsh/fscanf.html 中描述的所有逻辑和所有错误检查。 C++ iostream 没有格式字符串,但它仍然更通用:https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/istream.tcc#L156 'operator&gt;&gt;(int&amp; __n)'。

所以,标准库函数内部有更多的逻辑,更多的调用,更多的分支;而且它们更通用、更安全,应该在实际编程中使用。而这个“运动编程”竞赛允许用户使用足够快的标准库函数来解决任务,如果你能想象算法的话。要求作者或任务编写几个具有标准 i/o 功能的解决方案,以检查任务的时间限制是否正确以及任务是否可以解决。 (TopCoder 系统使用 i/o 更好,您不会实现 i/o,数据已经以某些语言结构/集合传递到您的函数中)。

有时sport programming 中的任务对内存有严格的限制:输入文件比允许的内存使用量大几倍,程序员无法将整个文件读入内存。例如:从输入文件中获取 2000 万位的单个很长数字并加 1,内存限制为 2 MB;您无法从文件中向前读取完整的输入编号;逆向逐块正确阅读是非常困难的;你只需要忘记加法的标准方法(列加法)并构建具有状态的 FSM(有限状态机),计数序列为9s。

【讨论】:

  • 我想知道通过setbuf( stdin, NULL ); 禁用stdin 上的缓冲或简单地调用read( STDIN_FILENO, str, 8333667 ); 而不是fread(...); 是否会提高性能。这可能会减少几毫秒,也许。
  • 使用 scanf/cin 禁用缓冲会使事情变慢(更多的系统调用),并且永远不应该使用这个“fastio”;任何形式,无论是 read 还是 fread,带或不带 setbuf。这只是不正确、不安全、不需要/不需要的过早优化,wiki.c2.com/?PrematureOptimization。 ”“我们应该忘记小的效率,比如大约 97% 的时间:过早的优化是万恶之源。然而,我们不应该放弃那关键的 3%""
  • 使用 scanf/cin 禁用缓冲会使事情变慢(更多的系统调用) 这将取决于底层实现 - 它很可能会导致 less 系统调用。 永远不应该使用这种“fastio” 一般情况下是这样,但是对于代码 IO 性能竞赛,几乎可以保证获胜者必须做类似的事情。
  • 安德鲁,竞争不是为了做I/O的微优化,而是计算答案;此任务将接受任何 I/O 样式并通过时间限制。不,禁用缓冲不会帮助 800000 scanf 更快;使用 libc 输入缓冲将有大约 1600 个 syscall,没有 - 800000 个 syscall 甚至数百万个(scanf does byte reads with inchar() = _IO_getc_unlocked (s)),并且 syscall 总是比 libcall 慢。
  • 贴出的代码没有内部计时,所以IO时间是和处理时间一起测量的。 不,禁用缓冲不会帮助 800000 scanf 更快; 您已经对其进行了基准测试?在不同的实现上?您确实知道,一般而言,将数据从文件读取到进程地址空间的绝对最快的方法是通过使用直接 IO 并读取页面对齐的缓冲区来禁用所有缓冲?不过,这通常不使用,因为如果您想再次读取数据,则必须一直返回磁盘。但如果你知道,你只会读一次……
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-25
  • 2014-07-25
  • 1970-01-01
  • 2017-12-23
  • 1970-01-01
  • 1970-01-01
  • 2018-02-04
相关资源
最近更新 更多