假设多达大约 1,000,000 百万个字符的时间测量值低于测量阈值,std::cout
并且stdout
使用批量写入(例如std::cout.write(str, size)
)的形式进行写入和写入,我猜想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。锁的成本将迅速减少。
顺便说一句,只是为了创建合理级别的游乐场:除了优化之外,您还应该std::sync_with_stdio(false)
在使用 C++ 标准流对象时调用。