Browse Source

init-array-bench: add a section about C++ constructors

Xℹ Ruoyao 5 years ago
parent
commit
d453346c5a
2 changed files with 75 additions and 1 deletions
  1. 74 0
      content/post/init-array-benchmark.md
  2. 1 1
      themes/academic

+ 74 - 0
content/post/init-array-benchmark.md

@@ -235,3 +235,77 @@ int main()
 不得不使用远大于实际比赛题目限制的内存),因此随便怎么初始化都行
 (当然不能写时间复杂度与输入规模呈二次关系的初始化,
  例如多组数据输入时每组都 `memset` 整个数组就可能超时)。
+
+## 关于构造函数的补充说明
+
+对于一些评测系统和题目,
+我们发现在代码中添加构造函数会导致运行时间数据变得非常难看。
+例如:
+
+```c++
+struct node
+{
+	int u, v;
+#if defined(HAVE_CONSTRUCTOR) && HAVE_CONSTRUCTOR
+	node(): u(0), v(0) {}
+#endif
+};
+
+node arr[500000000];
+
+int main() {}
+```
+
+我们不加 `-DHAVE_CONSTRUCTOR=1` 编译,得到的程序几乎不消耗时间。
+而加了 `-DHAVE_CONSTRUCTOR=1` 后,得到的程序会消耗内核态时间 $0.24$ 秒,
+用户态时间 $0.13$ 秒[^1]。这说明,在前一种情况下,初始化仍然是按照 BSS
+段的机制进行的,只要我们不使用数组,就不会真的进行初始化。而在后一种情况下,
+编译器直接生成了初始化整个数组的代码。
+
+[^1]: 在编写补充说明之前,测试环境的内存升级为了 DDR4 3200 MHz,
+故结果与之前的测量结果没有可比性。
+
+对于一些 OI 题目来说,这种行为会导致总的运行时间显著增加。例如,
+如果某题目规定对于 $100%$ 的数据有 $1 \leq n \leq 10^6$,
+而对于 $50%$ 的数据有 $1 \leq n \leq 1000$,那么加了构造函数后,
+即使对于 $n$ 较小的数据,程序在启动时也会初始化 $10^6$ 个元素。
+而不加构造函数的程序实际上只会初始化 $1000$ 个元素。
+洛谷等评测系统会将多组数据的运行时间叠加起来,作为总运行时间,
+这就会产生可观的差距。
+
+对此一种解决方法是使用 `= default()` 显式指定默认构造函数,例如:
+
+```c++
+struct node
+{
+	int u, v;
+	node() = default;                       // default constructor
+	node(int a, int b = 0) : u(a), v(b) {}  // nontrivial constructor
+};
+
+node arr[500000000];
+
+int main()
+{
+	node n(1, 2);
+}
+```
+
+这里我们使用构造函数的目的是方便构造非 $0$ 的元素。
+然而如果我们只定义上面的 `nontrivial constructor`,
+则编译器会拒绝自动生成默认构造函数,导致无法声明数组。
+如果我们手写一个构造函数,就会像前面讨论的一样,引入额外的初始化。
+而使用 `node() = default;` 这样的默认构造函数就能解决这一问题。
+但是需要注意的是,默认构造函数的行为与构造一个传统的 C 结构体是完全一致的,
+它甚至不会初始化该类型局部变量的各字段 (除非该字段的类型本身有构造函数)。
+例如:
+
+```c++
+int main()
+{
+	node n;
+	return n.u;
+}
+```
+
+如果 `n` 使用了 `default` 默认构造函数,该程序的行为是未定义的。

+ 1 - 1
themes/academic

@@ -1 +1 @@
-Subproject commit a140c554daf0504290f1a1801eebd635a30a56a5
+Subproject commit 27fbeab49170ed6a4bb73b424e3b582f68d1a9c0