【问题标题】:C++ fastest cin for reading stdin?用于读取标准输入的 C++ 最快 cin?
【发布时间】:2013-02-23 03:54:15
【问题描述】:

我在 Linux 上使用 cachegrind 分析了一个计算量很大的 C++ 程序。令人惊讶的是,我的程序的瓶颈并不在于任何排序或计算方法……而是在于读取输入。

这是 cachegrind 的屏幕截图,以防我对分析器结果的解释有误(参见 scanf()):

我希望我的说法是正确的,scanf() 占用了我 80.92% 的运行时间。

我使用cin >> int_variable_here 读取输入,如下所示:

std::ios_base::sync_with_stdio (false); // Supposedly makes I/O faster
cin >> NumberOfCities;
cin >> NumberOfOldRoads;
Roads = new Road[NumberOfOldRoads];

for (int i = 0; i < NumberOfOldRoads; i++)
{
    int cityA, cityB, length;    

    cin >> cityA;
    //scanf("%d", &cityA);    // scanf() and cin are both too slow
    cin >> cityB;
    //scanf("%d", &cityB);
    cin >> length;
    //scanf("%d", &length);

    Roads[i] = Road(cityA, cityB, length);
}

如果您没有发现此输入读取代码有任何问题,您能否推荐一种更快的读取输入的方法?我正在考虑尝试getline()(在等待回复的同时进行处理)。我的猜测是 getline() 可能运行得更快,因为它必须进行更少的转换,并且它解析流的总次数更少(只是我的猜测,尽管我最终也必须将字符串解析为整数)。

我所说的“太慢”的意思是,这是一个较大的家庭作业的一部分,它会在一段时间后超时(我相信是 90 秒)。我很确定瓶颈就在这里,因为我故意注释掉了程序其余部分的主要部分,但它仍然超时。我不知道讲师在我的程序中运行了哪些测试用例,但它一定是一个巨大的输入文件。那么,我可以用什么来最快地读取输入?

输入格式严格:3个整数,每行一个空格隔开,多行:

示例输入:

7 8 3
7 9 2
8 9 1
0 1 28
0 5 10
1 2 16

我需要从每一行的整数中创建一个Road

另外请不要将输入重定向到我的程序到标准输入 (myprogram &lt; whatever_test_case.txt)。我没有阅读特定的文件。我刚读到cin

更新

使用Slava's方法:

输入读取似乎花费的时间更少,但它仍然超时(可能不再是由于输入读取)。 Slava 的方法在Road() ctor 中实现(从main 下移2 个)。所以现在它需要 22% 的时间,而不是 80%。我正在考虑优化 SortRoadsComparator(),因为它被称为 26,000,000 次。

比较代码:

// The complexity is sort of required for the whole min() max(), based off assignment instructions
bool SortRoadsComparator(const Road& a, const Road& b)
{
    if (a.Length > b.Length) 
        return false;

    else if (b.Length > a.Length) 
        return true;

    else
    {
        // Non-determinism case
        return ( (min(a.CityA, a.CityB) < min(b.CityA, b.CityB)) ||
            (
            (min(a.CityA, a.CityB) == min(b.CityA, b.CityB)) && max(a.CityA, a.CityB) < max(b.CityA, b.CityB)                                     
            )
            );
    }
}

使用enhzflep's方法

考虑解决

我将考虑解决这个问题,因为瓶颈不再是读取输入。 Slava 的方法对我来说是最快的。

【问题讨论】:

  • 在你的情况下cin 一个文件。如果您想查看 scanf 是否真的是瓶颈,您应该从内存中读取(即预先将整个文件读入内存,并在配置文件中忽略它)。
  • 我会试试这个。我将使用getline(cin, string, EOF) 的重载,它应该一次全部阅读。我先用\n 分割每一行,然后用` ` 分割每一行得到每个整数。这听起来有效率吗?
  • 300 万个号码要扫描?应该在第二。如果它伤害使用二进制格式。
  • @ÖöTiib 我希望我能真正验证为什么这需要超过 3 分钟才能运行,但我不能(测试用例在远程机器上运行)。
  • 您还应该注意到scanf 占用了 80% 的时间,而 vfscanf 占用的时间相同,这意味着大部分时间都花在了 I/O 上。

标签: c++ performance


【解决方案1】:

众所周知,流非常慢。但这并不是什么大惊喜 - 他们需要处理本地化、条件等。一种可能的解决方案是逐行读取文件 std::getline( std:::cin, str ) 并通过类似的方式将字符串转换为数字这个:

std::vector<int> getNumbers( const std::string &str )
{
   std::vector<int> res;
   int value = 0;
   bool gotValue = false;
   for( int i = 0; i < str.length(); ++i ) {
      if( str[i] == ' ' ) {
         if( gotValue ) res.push_back( value );
         value = 0;
         gotValue = false;
         continue;
      }
      value = value * 10 + str[i] - '0';
      gotValue = true;
   }
   if( gotValue ) res.push_back( value );
   return res;
}

我没有测试这段代码,写它是为了展示这个想法。我假设您不希望在输入中得到除空格和数字之外的任何内容,因此它不会验证输入。

首先要优化排序,您应该检查是否真的需要对整个序列进行排序。对于比较器,我会编写方法 getMin() getMax() 并将该值存储在对象中(而不是一直计算它们):

bool SortRoadsComparator(const Road& a, const Road& b)
{
    if( a.Length != b.Length ) return a.Length < b.length;
    if( a.getMin() != b.getMin() ) return a.getMin() < b.getMin();
    return a.getMax() < b.getMax();
}

如果我了解您当前的比较器如何正常工作。

【讨论】:

  • 这种解析字符串的自定义方法实际上是否比拆分字符串并将其转换为 int 更快(甚至不确定该转换是如何完成的)?
  • 等等,为什么不直接使用atoi()?见this post
  • 你可以比较一下,我觉得应该很快。首先,它很简单。第二 - 它只扫描一次字符串以进行拆分和转换。无论如何它会比 std::cin 更快。
  • @Jason: strtol 也许可以,但永远不要使用 atoi,因为后者使错误检查太容易被忽略且难以实现。
  • 要使用atoi,您需要将字符串拆分为扫描的数字。而且 atoi 也不是世界上最快的函数。但是在您的情况下,我认为您无法检测到差异,如果对您来说更容易,您可以使用 atoi 或 strtol。
【解决方案2】:

正如 Slava 所说,就性能(和可执行文件大小)而言,流(即 cin)是绝对的猪

考虑以下两种方法:

start = clock();
std::ios_base::sync_with_stdio (false); // Supposedly makes I/O faster
cin >> NumberOfCities >> NumberOfOldRoads;
Roads = new Road[NumberOfOldRoads];
for (int i = 0; i < NumberOfOldRoads; i++)
{
    int cityA, cityB, length;
    cin >> cityA >> cityB >> length;
    Roads[i] = Road(cityA, cityB, length);
}
stop = clock();
printf ("time: %d\n", stop-start);

start = clock();
fp = stdin;
fscanf(fp, "%d\n%d\n", &NumberOfCities, &NumberOfOldRoads);
Roads = new Road[NumberOfOldRoads];
for (int i = 0; i < NumberOfOldRoads; i++)
{
    int cityA, cityB, length;
    fscanf(fp, "%d %d %d\n", &cityA, &cityB, &length);
    Roads[i] = Road(cityA, cityB, length);
}
stop = clock();
printf ("time: %d\n", stop-start);

单向运行 5 次(输入文件包含 1,000,000 个条目 + 前 2 个“控制”行),我们得到了以下结果:

  1. 使用 cin 没有 方向不与 stdio 同步 8291、8501、8720、8918、7164(平均 8318.3)

  2. 使用 cin with 方向不与 stdio 同步 4875、4674、4921、4782、5171(平均 4884.6)

  3. 使用 fscanf 1681、1676、1536、1644、1675(平均 1642.4)

因此,很明显,我们可以看到 sync_with_stdio(false) 方向确实有帮助。人们还可以看到 fscanf 用 cin 击败了每一种方法。事实上,fscanf 方法几乎比更好的 cin 方法快 3 倍,快 5 倍 strong> 在没有被告知避免与 stdio 同步时比 cin 强。

【讨论】:

  • 嗯,好像变慢了?程序总运行时间的相对 42% 花费在 Slava 的方法上,而 22%。
  • @Jason - 嗯。我没有尝试 Slava 的代码 - 只是坚持我熟悉的内容。看起来我有一些新的东西要熟悉。感谢您的反馈! :)
  • 非常感谢您写下这篇文章。我如何测试它与您发布的几乎完全相同。我确认它确实有效,只是它似乎并没有更快在我的应用程序中。这真的可能意味着在大多数情况下你的速度更快,而我的情况很奇怪(或奇怪地接近)。你的基准比我的情况图表更重要。再次感谢!
  • 当然我的代码应该更快。但这并不意味着它更好。一方面 iostream、sscanf、strtol 等是更通用的解决方案,另一方面他们必须检查他们的输入。正如我所提到的,如果您确定输入是正确的并且只包含正整数,那么我的函数将运行得更快,因为它针对这种特殊情况高度专业化并且不会验证输入。
【解决方案3】:
inline void S( int x ) {
x=0;
while((ch<'0' || ch>'9') && ch!='-' && ch!=EOF) ch=getchar_unlocked();
if (ch=='-')
sign=-1 , ch=getchar_unlocked();
    else
sign=1;
do
x = (x<<3) + (x<<1) + ch-'0';
while((ch=getchar_unlocked())>='0' && ch<='9');
x*=sign;
}

您可以将此功能用于任何类型的数字输入,只需更改参数类型即可。 这将比 std scanf 运行得更快。

如果您想节省更多时间,最好的办法是使用 fread() 和 fwrite(),但在这种情况下,您必须自己操作输入。 为了节省时间,您应该使用 fread() 在一次调用中从标准输入流中读取大量数据。这将减少 I/O 调用的数量,因此您会看到很大的时间差异。

【讨论】:

    猜你喜欢
    • 2014-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多