[英]Why this method using putchar_unlocked is slower than printf and cout to print strings?
我正在研究以编程和竞赛为基础加速输入和输出处理的方法。
我目前正在使用线程不安全的putchar_unlocked函数来打印一些测试。 我相信,如果函数实现得当,由于该函数具有线程可解锁的特性,那么该函数比cout e printf更快。
我实现了一个以这种方式打印字符串的函数(根据我的观点,这非常简单):
void write_str(char s[], int n){
int i;
for(i=0;i<n;i++)
putchar_unlocked(s[i]);
}
我使用大小为n且正好为n个字符的字符串进行了测试。
但这是三个中最慢的一个,在此图中如何看到输出写入次数与时间(以秒为单位)的关系:
为什么最慢?
假设最多约1万亿个字符的时间测量值低于测量阈值,并且使用批量写入的形式(例如std::cout.write(str, size)
)对std::cout
和stdout
进行写操作,我猜想putchar_unlock()
花费大部分时间来实际更新数据结构的某些部分,而不是放置字符。 其他批量写入操作会将数据批量复制到缓冲区中(例如,使用memcpy()
),并在内部仅一次更新数据结构。
也就是说,代码看起来像这样(这是pidgeon代码,即,仅大致显示正在发生的事情;实际代码至少要稍微复杂一些):
int putchar_unlocked(int c) {
*stdout->put_pointer++ = c;
if (stdout->put_pointer != stdout->buffer_end) {
return c;
}
int rc = write(stdout->fd, stdout->buffer_begin, stdout->put_pointer - stdout->buffer_begin);
// ignore partial writes
stdout->put_pointer = stdout->buffer_begin;
return rc == stdout->buffer_size? c: EOF;
}
相反,代码的批量版本正在按照这种方式做一些事情(使用C ++表示法,因为它更容易成为C ++开发人员;这又是pidgeon代码):
int std::streambuf::write(char const* s, std::streamsize n) {
std::lock_guard<std::mutex> guard(this->mutex);
std::streamsize b = std::min(n, this->epptr() - this->pptr());
memcpy(this->pptr(), s, b);
this->pbump(b);
bool success = true;
if (this->pptr() == this->epptr()) {
success = this->this->epptr() - this->pbase()
!= write(this->fd, this->pbase(), this->epptr() - this->pbase();
// also ignoring partial writes
this->setp(this->pbase(), this->epptr());
memcpy(this->pptr(), s + b, n - b);
this->pbump(n - b);
}
return success? n: -1;
}
第二个代码可能看起来更复杂,但对于30个字符仅执行一次。 很多检查都移到了有趣的地方。 即使完成了一些锁定,它仍在锁定无竞争的互斥锁,并且不会过多地抑制处理。
尤其是当不进行任何分析时,使用putchar_unlocked()
的循环不会得到太多优化。 特别是,代码不会进行矢量化处理,这会导致人工循环上的立即因子至少约为3,但可能甚至接近16。 锁的费用将迅速减少。
顺便说一句,只是为了创建合理级别的游乐场:除了优化之外,在使用C ++标准流对象时,还应该调用std::sync_with_stdio(false)
。
选择更快的输出字符串的方法会与所使用的平台,操作系统,编译器设置和运行时库发生冲突,但是有些概括可以帮助理解选择的内容。
首先,考虑到与一次字符相比,操作系统可能具有一种显示字符串的方式,如果是这样,一次遍历一次用于字符输出的系统调用自然会为系统的每次调用带来开销,与一个系统调用处理字符数组的开销相反。
基本上,这就是您遇到的系统调用的开销。
与putchar相比,putchar_unlocked的性能增强可能是相当大的,但仅在这两个函数之间。 此外,大多数运行时库都没有putchar_unlocked(我在较早的MAC OS X文档中找到了,但在Linux或Windows上却没有)。
也就是说,无论是锁定还是未锁定,每个字符仍然会有开销,可以在处理整个字符数组的系统调用中消除这些开销,并且这些概念可以扩展到输出到文件或其他设备,而不仅仅是控制台。
我个人的猜测是,printf()会以块的形式进行操作,只需要偶尔为每个块传递应用程序/内核边界。
putchar_unlocked()对写入的每个字节执行此操作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.