c语言中输入一堆整数,如何在C ++中快速输入数百万个整数?

我正在执行有关C ++中堆栈的数据结构编程任务。

在此作业中,我应该读取很多整数(在最坏的情况下,我应该读取1,600,000个整数),最后输出一些字符串。

作为一名学生,我提交了cpp源文件,然后网站对我的源代码进行了评估和评分。我得到了100%,但我想做得更好。

此分配的时间限制为2秒,我的源代码的执行时间为128毫秒。但是,成绩最好的学生仅用52毫秒即可完成任务。所以我想知道如何使我的代码更快。

我的源代码主要包含三个部分:

使用cin从OnlineJudge系统中读取很多整数(最多1,600,000个整数)。

尝试找到解决方案并将其存储在char数组中。

使用cout输出char数组。

OnlineJudge告诉我代码的执行时间。第一部分花费100毫秒,第二部分花费20毫秒,而第三部分花费12毫秒。因此,如果我想使代码更快,则应该提高输入速度。

OnlineJudge的输入是这样的:

5 2

1 2 3 5 4

第一行是两个整数n和m,第二行是n个由空格分隔的整数。限制为:1 <= n <= 1,600,000和0 <= m <= 1,600,000。

为了读取超过一百万个整数,我的代码是这样的:

#include

using namespace std;

int main()

{

std::ios::sync_with_stdio(false);

cin.tie(NULL);

int *exit = new int[1600000];

cin>>n>>m;

for (int i=0;i

cin>>exit[i];

return 0;

}

如果n小,则OnlineJudge会说执行时间为0毫秒。

如果n非常大,例如160万OnlineJudge说此代码需要100毫秒。如果我删除

std::ios::sync_with_stdio(false);

cin.tie(NULL);

然后代码需要424毫秒。但是,在此作业中必须读取整数,因此我很好奇顶尖的学生如何在52毫秒内完成" cin,查找解决方案,退出"。

您对提高输入速度有任何想法吗?

2019.4.17:有人建议使用vector或std :: from_chars,但是在此分配中,这些被禁止。

如果我写

#include

要么

#include

要么

#include

然后OnlineJudge说"编译错误"。

有人建议使用scanf,我的代码如下:

for (int i=0;i

scanf("%d", &exit[i]);

但是执行时间是120毫秒。顺便说一句,我不认为scanf比cin快,在C ++程序中使用scanf()比使用cin快吗?

有人建议使用getline。我很少使用此功能,我的代码如下:

stringstream ss;

string temp;

getline(cin, temp);

ss<>n;ss>>m;

ss.clear();temp.clear();

getline(cin, temp);ss<

for (int i=0;i

ss>>exit[i];

执行时间也是120毫秒。

有人建议使用mmap。我以前从未听说过此功能。看来此功能仅在Unix中可用?但是我正在使用Visual Studio2010。我的代码是这样的:

#include

#include

//to load 1,600,000 integers

int *exit = static_cast(mmap(NULL,1600*getpagesize(),PROT_READ,MAP_ANON|MAP_SHARED,0,0));

for (int i=0;i

cin>>*(exit+i);

OnlineJudge说"运行时错误(信号11)"而不是"编译错误",信号11表示"无效的内存引用",该信号在发出无效的虚拟内存引用或分段错误(即执行错误)时发送到进程细分违规。我不知道我的mmap有什么问题,希望你能告诉我。

2019.4.22:谢谢您的帮助。现在我成功解决了这个问题。关键功能是mmap。代码如下:

#include

cin.tie(NULL);

std::ios::sync_with_stdio(false);

string temp;

int n,m;

int *exit = new int[1600000];

const int input_size = 13000000;

void *mmap_void = mmap(0,input_size,PROT_READ,MAP_PRIVATE,0,0);

char *mmap_input = (char *)mmap_void;

int r=0,s=0;

while (mmap_input[s]'9') ++s;

while (mmap_input[s]>='0' && mmap_input[s]<='9')

{ r=r*10+(mmap_input[s]-'0');++s; }

n=r;r=0;

while (mmap_input[s]'9') ++s;

while (mmap_input[s]>='0' && mmap_input[s]<='9')

{ r=r*10+(mmap_input[s]-'0');++s; }

m=r;r=0;

while (mmap_input[s]'9') ++s;

for (int i=0;i

{

while (mmap_input[s]>='0' && mmap_input[s]<='9')

{ r=r*10+(mmap_input[s]-'0');++s; }

++s;

exit[i]=r;r=0;

}

mmap并将字符转换为整数的执行时间为8毫秒。现在,此作业的总执行时间为40毫秒,比52毫秒要快。

不要读取数据,将其映射。

欢迎使用C ++。您应该了解的第一件事是如何有效地使用标准库容器(如std::vector),而不是对new[]进行C样式数组的原始分配。在C ++中,您100%负责内存管理,因此进行手动分配意味着要小心在正确的时间和地点添加相应的delete[]调用。标准库容器可以为您处理此问题。

众所周知,标准库中的iostream对于格式化输入来说相当慢,但是它们是通用的-例如,它们支持不同的语言约定等。如果您需要速度,我建议至少逐行阅读并手动将字符串转换为整数。同样,如果您需要基于thouse编号计算某些内容,则可以更快地就位进行处理,并避免将它们全部存储在内存中。

众所周知,通过std::流格式化输入/输出不是最快的。另一个学生可能正在使用C标准库中的scanf()之类的东西,它们通常更快。但是,即使C标准库函数也必须遵守当前语言环境设置的格式设置规则。您很可能只需要读取整数而不必担心任何特殊格式。除了自己解析整数之外,最快的事情可能是std :: from_chars…

刘嘉成,您好,欢迎来到Stack Overflow。以下是一些可能对您有所帮助的类似问题:stackoverflow.com/questions/16826422/ stackoverflow.com/questions/4351371/

谢谢你的建议。但是抱歉,我昨天忘了解释。在此分配中,如果使用" #include ",则OnlineJudge说编译错误。 @塔德曼

@MichaelKenzel我尝试了scanf,需要120毫秒。我编辑问题以包含scanf代码。 #include 在此分配中被禁止。

好吧,我想很遗憾,您必须编写自己的解析器。幸运的是,用于纯十进制整数的基本解析器很容易编写。感谢您向我提出有关std::cin性能的其他问题!那很有趣,我不知道与C stdio的同步是一个主要瓶颈。

两个问题。看起来stackoverflow.com/a/42446361/1466970说,先解开std::cin.tie (nullptr);,然后关闭同步std::cout.sync_with_stdio(false);。不知道这是否有所作为。其次是像int i, *p = exit; for (i=0;i> *p;中那样使用指针有区别吗?

您需要使用这些整数解决的实际问题是什么?我想知道您是否可以避免完全解析它们。

@Slava我尝试了getline.I编辑问题以包括我的getline代码。执行时间为120毫秒。

我会手工编写一个状态机,其唯一目的是读取整数。我将读取大块输入数据,并将其传递给状态机。

为什么不问另一个学生呢?

@RichardChambers对不起,我尝试了您的建议,但两个建议都没有区别,谢谢。

@ n.m。嗯,该网站上的编程工作就像一个开放式课件。计分表显示按时间成本排序的前10名学生。公共信息包括昵称,分数,最坏情况,时间(毫秒),内存(KB)。我只知道前1名学生的昵称-Snickeen,但这没用。由于该网站需要user_id和密码,因此我之前没有发布过该网站。 dsa.cs.tsinghua.edu.cn/oj/course.shtml?courseid=58

@DanielH简单地说,将A表示为{1,2,……,n},将B表示为{1,2,……,n}的排列。任务是通过使用从A开始的堆栈来查看B是否可行。例如,如果A为{1,2,3,4,5},则B为{1,2,3,5,4} 。给定一个空堆栈,那么我们可以"按1,按1,按2,按2,按3,按3,按4,按5,按5,按4"。因此,{1,2,3,4,5}中的{12,3,5,4}是可行的。该网站中的原始问题较长,但该网站需要user_id和密码。

@WilliamPursell我尝试了mmap。我编辑问题以包含mmap代码。我不知道为什么OnlineJudge说错了。希望您能找到错误。谢谢。

我认为您在浪费时间在黑暗的房间里追逐一只黑猫。我们不知道另一个学生做了什么。也许他使用了内联汇编。也许他能够利用系统中的错误。没有办法知道这一点。您的程序正在完成这项工作,这就是唯一重要的事情。学会做一个更好的程序员,不要使用晦涩难懂的API来赢得几千微秒的收益。但是,无论什么漂浮着你的船。

通常,您无法映射标准输入,因此您要么没有讲完整的故事,要么做的是完全错误的事情。

" OnlineJudge说" Compilation error"法官会说出超出这些确切字眼的任何内容吗?如果不是,那么它就无法使用,您可能想向您的大学投诉或接受它并继续前进,因为您知道您正在被愚蠢的机器审判由无助的人编程。

@ n.m。感谢您的建议。我还怀疑在这个问题上花费时间是否有价值。也许我应该直接给课程老师发电子邮件,看看他有什么想法。

使用带有std::getline()的代码时,您没有进行任何更改,不是std::cin变慢,而是来自std::istream的格式化输入,并且使用std::cin还是std::stringstream实例都没有关系

@ n.m。如果stdin是常规文件,则mmap可以在stdin上完美找到。

@WilliamPursell多数民众赞成在一个很好的观察,我肯定这将值得与在线法官的几点加分。

您尝试mmap是(1)完全错误,(2)没有用。与通常的迷信相反,它不会比正常阅读快得多。

@ n.m .:能够优化软件是一项宝贵的技能,无论是它的直接收益还是一路走来所获得的知识。如果有人有兴趣学习如何更快地开发软件,请不要劝阻他们。

@EricPostpischil我雇用程序员。与寻找更快的罐装API来执行罐装任务的能力相比,我更重视优化算法的能力。因此,我鼓励对我有价值的技能。

@ n.m .:您在工作中的需求并不是世界上唯一有价值的需求。在很多代码中,算法是可获得的最佳复杂性,而需要做的是进一步优化以利用性能的所有方面。请不要阻止对学习的兴趣。

@EricPostpischil我将鼓励并促进我所需要的,并让其他人鼓励并促进他们所需要的。愿最好的一场胜利。

@ n.m .:我没有要求你不要鼓励或促进。我问你不要劝阻。这是有害的。

@EricPostpischil非常感谢您的鼓励。有一次我想放弃但您说过"能够优化软件是一项宝贵的技能",所以我一直在尝试其他方法。我昨天很挣扎,现在我使用mmap来解决任务。现在,我的代码的总执行时间为40毫秒,快于52毫秒。

一些想法:

使用std::scanf而不是std::istream读取整数。众所周知,由于多种原因,即使使用std::ios::sync_with_stdio(false)调用,后者也较慢。

通过将文件映射到内存中来读取文件。

解析整数比scanf和strtol更快。

例:

#include

int main() {

int n, m, a[1600000];

if(2 != std::scanf("%d %d", &n, &m))

throw;

for(int i = 0; i < n; ++i)

if(1 != std::scanf("%d", a + i))

throw;

}

您也可以展开scanf循环以在一个调用中读取多个整数。例如。:

#include

constexpr int step = 64;

char const fmt[step * 3] =

"%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"

"%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"

"%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"

"%d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d"

;

void main() {

int a[1600000];

int n, m;

if(2 != std::scanf("%d %d", &n, &m))

throw;

for(int i = 0; i < n; i += step) {

int expected = step < n - i ? step : n - i;

int* b = a + i;

int read = scanf(fmt + 3 * (step - expected),

b + 0x00, b + 0x01, b + 0x02, b + 0x03, b + 0x04, b + 0x05, b + 0x06, b + 0x07,

b + 0x08, b + 0x09, b + 0x0a, b + 0x0b, b + 0x0c, b + 0x0d, b + 0x0e, b + 0x0f,

b + 0x10, b + 0x11, b + 0x12, b + 0x13, b + 0x14, b + 0x15, b + 0x16, b + 0x17,

b + 0x18, b + 0x19, b + 0x1a, b + 0x1b, b + 0x1c, b + 0x1d, b + 0x1e, b + 0x1f,

b + 0x20, b + 0x21, b + 0x22, b + 0x23, b + 0x24, b + 0x25, b + 0x26, b + 0x27,

b + 0x28, b + 0x29, b + 0x2a, b + 0x2b, b + 0x2c, b + 0x2d, b + 0x2e, b + 0x2f,

b + 0x30, b + 0x31, b + 0x32, b + 0x33, b + 0x34, b + 0x35, b + 0x36, b + 0x37,

b + 0x38, b + 0x39, b + 0x3a, b + 0x3b, b + 0x3c, b + 0x3d, b + 0x3e, b + 0x3f);

if(read != expected)

throw;

}

}

另一个选择是手动解析整数(将文件映射到内存中将对此有所帮助,并且解析整数的算法比此方法和标准atoi/strtol快得多,请参见Fastware-Andrei Alexandrescu):

int main() {

int n, m, a[1600000];

if(2 != std::scanf("%d %d", &n, &m))

throw;

for(int i = 0; i < n; ++i) {

int r = std::getchar();

while(std::isspace(r))

r = std::getchar();

bool neg = false;

if('-' == r) {

neg = true;

r = std::getchar();

}

r -= '0';

for(;;) {

int s = std::getchar();

if(!std::isdigit(s))

break;

r = r * 10 + (s - '0');

}

a[i] = neg ? -r : r;

}

}

还有一个方法是将文件映射到内存中并更快地解析它:

#include

inline int find_and_parse_int(char const*& begin, char const* end) {

while(begin != end && std::isspace(*begin))

++begin;

if(begin == end)

throw;

bool neg = *begin == '-';

begin += neg;

int r = 0;

do {

unsigned c = *begin - '0';

if(c >= 10)

break;

r = r * 10 + static_cast(c);

} while(++begin != end);

return neg ? -r : r;

}

void main() {

boost::iostreams::mapped_file f("random-1600000.txt", boost::iostreams::mapped_file::readonly);

char const* begin = f.const_data();

char const* end = begin + f.size();

int n = find_and_parse_int(begin, end);

int m = find_and_parse_int(begin, end);

int a[1600000];

for(int i = 0; i < n; ++i)

a[i] = find_and_parse_int(begin, end);

}

基准源代码。

请注意,不同版本的编译器和标准库的结果可能会有很大的不同:

CentOS版本6.10,g ++-6.3.0,Intel Core i7-4790 CPU @ 3.60GHz

---- Best times ----

seconds,    percent, method

0.167985515,  100.0, getchar

0.147258495,   87.7, scanf

0.137161991,   81.7, iostream

0.118859546,   70.8, scanf-multi

0.034033769,   20.3, mmap-parse-faster

Ubuntu 18.04.2 LTS,g ++-8.2.0,Intel Core i7-7700K CPU @ 4.20 GHz

---- Best times ----

seconds,    percent, method

0.133155952,  100.0, iostream

0.102128208,   76.7, scanf

0.082469185,   61.9, scanf-multi

0.048661004,   36.5, getchar

0.025320109,   19.0, mmap-parse-faster

@MaximEgorushkin非常感谢您冗长而具体的回答。对于您的第一个想法,int array [1600000]不会造成任何问题,但对执行时间没有影响。对于第二个想法,我不知道您是否已阅读此问题:stackoverflow.com/questions/1042110/还有,"#include "或被禁止。 OnlineJudge说"编译错误",因为"没有这样的文件或目录"。因为您的答案太长,所以明天我会尝试其余的想法。谢谢。

@JiaChengLiu在我的istream基准测试中,我确实称呼std::ios::sync_with_stdio(false)。没有它,istream需要355毫秒。

@MaximEgorushkin,那么您应该使用更好的操作系统

在相当老的RH6上,大约需要10微秒。

Btw OP还确认使用自动数组vs动态分配完全不会影响执行时间。

在我的机器上(虽然这是一台服务器),使用new需要0.00290189s,而使用堆栈数组需要0.00271056s,才能为160万个元素的每个元素分配和分配值。然后,我计算出测量值之和,以防止优化程序消除访问。两种变体的时间在2.7ms到3ms之间变化。我看不出有什么区别。

@JiaChengLiu将代码修改为不使用或。

@MaximEgorushkin对不起,我今天忙于做作业。一天后,我会尝试您的想法,谢谢。

@MaximEgorushkin我昨天尝试了您的想法,而mmap是最快的。" cin with sync(false)"花费100毫秒," scanf-multi"花费88毫秒," getline并将字符手动转换为int"花费20毫秒," mmap并将字符手动转换为int"花费8毫秒。现在,我的代码的总执行时间为40毫秒,甚至比顶级学生的52毫秒还要快。非常感谢。编码很有趣。

@JiaChengLiu您可能希望将基准测试结果以及系统描述添加到github上的README.md中。

@JiaChengLiu在github版本中,mmap与MAP_POPULATE一起使用可减少页面错误的机会,然后仅前向顺序访问使其成为硬件预取器的最佳方案。

time of my source code is 128 milliseconds. However, the top student only used 52 milliseconds

要运行整个程序,这要进入误差范围。在现代OS上设置进程需要花费一些时间,无论输入输入数据的内容如何,??如果服务器是共享资源,任何资源争用都会出现问题。提交相同的确切代码有多少不同?

int *exit = new int[1600000];

内存分配是有代价的。在高性能循环等中,通常会完全避免使用它们,尽管单个分配不太可能在整体上产生重大差异。

Input of OnlineJudge is like this:

5 2

1 2 3 5 4

The 1st line is two integers n and m, the 2nd line is n integers separated by spaces. Restrictions are: 1<=n<=1,600,000 and 0<=m<=1,600,000. In order to read more than 1 million integers, my code is like this:

我发现std::cin等可能很慢,在某些情况下数字解析功能也会很慢。如果您可以一口气读完整行然后解析,那可能会更快。对于解析来说,如果您可以确保输入不合法,则通常以不安全的方式进行解析即可获得收益。

总是'分隔符吗?看起来是这样,您可以特殊情况结??尾。例如。将整个"行"读入缓冲区,然后将" n"替换为""。

是否知道位数?是始终为1,还是小于5的其他少量数字?

数字是否始终在有效范围内?

输入是否始终是有效数字,没有要检查的随机字符?

是否有负数?

了解这些事情,您可能会说:

/*1 or 2 digit int, space delimiter. Advance p number of consumed chars.*/

int parse_small_int(char **p)

{

int v = (*p)p[0] - '0';

char c2 = (*p)[1];

if (c2 == ' ') // 1 digit

{

return v;

}

else // assume 2 digit

{

v *= 10;

v += (c2 - '0')

(*p) += 2;

}

}

Do you have any ideas on improving input speed?

输出同样如此,您似乎没有显示代码,但是std :: cout可能同样慢。而且,如果您知道一些有关数字和允许的输出格式的信息,则可以轻松击败<

前导零有效吗?如果是这样,则可以为最大允许值编写无条件格式化程序。

对预先分配的缓冲区进行这种格式化,然后打印整行。

例如

// always write 2 chars to p

void format_int_2_digit(int i, char *p)

{

p[0] = '0' + (i / 10);

p[1] = '0' + (i % 10);

}

另一种可能性是绕过C ++甚至C库,尽管在您的作业中可能不允许这样做。

例如,在Linux上,可以将read和write函数与STDIN_FILENO和STDOUT_FILENO一起使用。我从未亲自将这些与CRT版本进行比较,但是也许会有明显的不同。在Windows上有

ReadConsole,WriteConsole等,或先使用GetStdHandle,再使用ReadFile,WriteFile等。我再也没有测量这些。

您可能还希望能够读取负整数,包括奇怪的数字。

这就是为什么我说取决于要求。如果您最终至少需要说strtol全部,那么您将不会获得任何收益或尝试击败它的任何东西,只需使用scanf,strtol等。但是当您开始将所有东西从功能列表中删除时,有时您可以以明显的优势击败运行时功能。

strtol可以被2倍击败,请参阅Fastware-Andrei Alexandrescu。

@MaximEgorushkin有代码吗?我浏览了一下,没有看到功能的完整替代,因此我认为它属于我所说的,可以做一些特定的事情,甚至可以击败最佳优化的CRT。仅读取1或2位数字将快2倍多,而精确地一位数字甚至更快,并且所有这些事情都应该比scanf以及肯定地istream >>快很多。取决于要求的最佳解决方案。

有代码,可以替代标准代码,即功能完善。病留给你去探索。

@MaximEgorushkin确切地说,我再次浏览了一下,这些都是很好的优化技巧,请确保。我在想像strtol如何自动检测0,0x,0x或采用base参数。或者strtoi根本不存在,因此如果您特别想要int,则可能需要进行额外的范围检查(在想要完全安全的情况下,例如在64位linux上,其中long的范围比int大)。

@MaximEgorushkin Etiher的方式,我的意思是您可以击败scanf,cin等。问题/降票仅是我提出了不安全解析的可能性,而这有可能比最好的更快 优化的解决方案,当然如上所述,不安全吗?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值