我正在执行有关C ++中堆栈的数据结构编程任务。
在此作业中,我应该读取很多整数(在最坏的情况下,我应该读取1,600,000个整数),最后输出一些字符串。
作为一名学生,我提交了cpp源文件,然后网站对我的源代码进行了评估和评分。我得到了100%,但我想做得更好。 此分配的时间限制为2秒,我的源代码的执行时间为128毫秒。但是,成绩最好的学生仅用52毫秒即可完成任务。所以我想知道如何使我的代码更快。
我的源代码主要包含三个部分:
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 <iostream>
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<n;++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 <vector>
或
#include <charconv>
或
#include <array>
然后OnlineJudge说“编译错误”。
有人建议使用scanf,我的代码如下:
for (int i=0;i<n;++i)
scanf("%d", &exit[i]);
但是执行时间是120毫秒。顺便说一句,我认为scanf不比cin快Using scanf() in C++ programs is faster than using cin?
有人建议使用getline。我很少使用此功能,我的代码如下:
stringstream ss;
string temp;
getline(cin, temp);
ss<<temp;ss>>n;ss>>m;
ss.clear();temp.clear();
getline(cin, temp);ss<<temp;
for (int i=0;i<n;++i)
ss>>exit[i];
执行时间也是120毫秒。
有人建议使用mmap。我以前从未听说过此功能。看来此功能仅在Unix中可用?但是我正在使用Visual Studio2010。我的代码是这样的:
#include <unistd.h>
#include <sys/mman.h>
//to load 1,600,000 integers
int *exit = static_cast<int*>(mmap(NULL,1600*getpagesize(),PROT_READ,MAP_ANON|MAP_SHARED,0,0));
for (int i=0;i<n;++i)
cin>>*(exit+i);
OnlineJudge说“运行时错误(信号11)”而不是“编译错误”,信号11表示“无效的内存引用”,该信号在发出无效的虚拟内存引用或分段错误时发送到进程,即执行细分违规时。我不知道我的mmap有什么问题。希望你能告诉我。
2019.4.22:谢谢您的帮助。现在我成功解决了这个问题。关键功能是mmap。代码如下:
#include <sys/mman.h>
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]<'0' || 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]<'0' || 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]<'0' || mmap_input[s]>'9') ++s;
for (int i=0;i<n;++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的执行时间和将char转换为整数需要8毫秒。现在,此作业的总执行时间为40毫秒,比52毫秒要快。
答案 0 :(得分:4)
一些想法:
std::scanf
而不是std::istream
读取整数。即使有std::ios::sync_with_stdio(false)
调用,后者也会由于多种原因而变慢。scanf
和strtol
更快。示例:
#include <cstdio>
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 <cstdio>
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 <boost/iostreams/device/mapped_file.hpp>
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<int>(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);
}
请注意,不同版本的编译器和标准库的结果可能会有很大差异:
---- 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
---- 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
答案 1 :(得分:-1)
我的源代码的时间是128毫秒。但是,成绩最好的学生只用了52毫秒
要运行整个程序,这正在进入误差范围。在现代OS上设置进程需要花费一些时间,无论输入输入数据的内容如何,如果服务器是共享资源,任何资源争用都会出现问题。提交相同的确切代码有多少不同?
int * exit =新的int [1600000];
内存分配是有成本的。在高性能循环等中,通常会完全避免使用它们,尽管单个分配不太可能对整体产生重大影响。
OnlineJudge的输入如下:
5 2 1 2 3 5 4
第一行是n和m的两个整数,第二行是由空格分隔的n个整数。限制为:1 <= n <= 1,600,000和0 <= m <= 1,600,000。为了读取超过一百万个整数,我的代码是这样的:
我发现std::cin
等可能很慢,在某些情况下,数字解析功能也会很慢。如果您可以一口气读完整行然后解析,那可能会更快。解析收益通常来自以不安全的方式解析,前提是您可以随意输入,例如
了解这些内容后,您可能会说:
/*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;
}
}
您对提高输入速度有任何想法吗?
输出也一样,您似乎没有显示代码,但是std :: cout可能同样慢。而且,如果您对数字和允许的输出格式有所了解,则可以轻松击败<<
,std::to_string
,itoa
等。
例如
// 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
等。我再也没有测量这些。