使用C++中的循环中的复杂对象来避免最小范围低效的技术?

Mar*_* Ba 16 c++ performance loops

问题首先

在C++中是否有一个优雅的解决方案,以防止必须声明复杂的对象变量,这些变量仅出于效率原因而仅在循环外的循环中使用?

详细解释

一位同事提出了一个有趣的观点.我们的代码策略,其中说明(释义):始终对变量使用最小范围,并在第一次初始化时声明变量.

编码指南示例:

// [A] DO THIS
void f() {
  ...
  for (int i=0; i!=n; ++i) {
    const double x = calculate_x(i);
    set_squares(i, x*x);
  }
  ...
}

// [B] DON'T do this:
void f() {
  int i;
  int n;
  double x;
  ...
  for (i=0; i!=n; ++i) {
    x = calculate_x(i);
    set_squares(i, x*x);
  }
  ...
}
Run Code Online (Sandbox Code Playgroud)

这一切都很好,在你从原始类型转移到对象之前,这肯定没有错.(对于某种界面)

例:

// [C]
void fs() {
  ...
  for (int i=0; i!=n; ++i) {
    string s;
    get_text(i, s); // void get_text(int, string&);
    to_lower(s);
    set_lower_text(i, s);
  }
  ...
}
Run Code Online (Sandbox Code Playgroud)

这里,字符串s将被破坏,它的内存释放每个循环周期,然后每个循环该get_text函数将不得不为s缓冲区重新分配内存.

写作显然更有效:

  // [D]
  string s;
  for (int i=0; i!=n; ++i) {
    get_text(i, s); // void get_text(int, string&);
    to_lower(s);
    set_lower_text(i, s);
  }
Run Code Online (Sandbox Code Playgroud)

因为现在s缓冲区中分配的内存将在循环运行之间保留,我们很可能会节省分配.

免责声明: 请注意:由于这是循环,我们正在谈论内存分配,我认为通常考虑这个问题是不成熟的优化.当然有些情况和循环,开销无关紧要; 但是n,开发人员最初期望的那种唠叨倾向会更大,并且代码有一种唠叨的倾向,即在性能确实重要的环境中运行.

无论如何,现在对于"通用"循环结构来说,更有效的方法是违反代码局部性并将复杂对象声明到位,"以防万一".这让我感到很不安.

请注意,我考虑这样写:

// [E]
void fs() {
  ...
  {
    string s;
    for (int i=0; i!=n; ++i) {
      get_text(i, s); // void get_text(int, string&);
      to_lower(s);
      set_lower_text(i, s);
    }
  }
  ...
}
Run Code Online (Sandbox Code Playgroud)

解的可读性遭受甚至更多!

进一步思考,get_text函数的接口无论如何都是非惯用的,因为无论如何out params是如此,并且"good"接口将按值返回:

  // [F]
  for (int i=0; i!=n; ++i) {
    string s = get_text(i); // string get_text(int);
    to_lower(s);
    set_lower_text(i, s);
  }
Run Code Online (Sandbox Code Playgroud)

在这里,我们不会为内存分配支付双倍的费用,因为它极有可能s通过RVO从返回值构造,因此对于[F],我们支付与 [C]中相同的分配开销.然而, [C]情况不同,我们无法优化此接口变体.

因此,底线似乎是使用最小范围(可能)会损害性能并使用干净的界面我至少认为按值返回比使用out-ref-param的东西更清洁会阻止优化机会 - 至少在一般情况下.

问题是没有这么多,一个人必须要放弃,有时为了效率干净的代码,问题是,尽快启动离散事件找到这样的特殊情况下,整个编码指南(见[A],[B])失去权力.

现在的问题是:见第一段

jus*_*tin 1

我要么:

  • 为这些重量级人物破例。像“D”并注意您可以根据需要限制范围。
  • 允许辅助函数(字符串也可以是参数)
  • 如果您确实不喜欢这些,您可以for使用保存计数器/迭代器和临时对象的多元素对象在循环范围中声明本地对象。std::pair<int,std::string>尽管专门的容器可以减少语法噪音,但这将是一种选择。

(在很多情况下,out 参数会比 RVO 样式更快)