何时对数组使用指针与使用访问运算符的通用规则是什么?
What is a good general rule for when to use pointers for arrays versus using access operators?
访问运算符示例:
void print_elements ( int * arr, int n )
{
for (int k = 0; k < n; ++k) printf("%d\n", arr[k]);
}
指针运算例子:
void print_elements ( int * arr, int n )
{
for (int * pa(arr), * pb(arr+n); pa != pb; ++pa) printf("%d\n", *pa);
}
第二个涉及循环的 2 个变量,pa
和 pb
,而第一个只涉及一个额外的变量,k
。再一次,你在做的那一秒
increment pa by 1, dereference
而在第一个你做的
increment k by 1, add k to the pointer arr, derefence
以便第一个使用较少的操作来遍历循环。
综合考虑,哪个更快?对于整数类型,比较器 <
是否比 !=
快?使用 <
似乎 "safer" 因为总是有一种非理性的恐惧,即像 pa != pb
这样的条件作为条件失败,因为如果 pa
可以跳过 pb
如果循环被重构。将指针递增 1
通常比递增大于 1
更快吗?我想考虑一切可能。
这将属于 "premature optimization" 的范围。除非你有特定的(衡量的)理由,否则你通常应该更喜欢最简单和最直接的代码(在这种情况下是你的第一个函数)。现在任何编译器都有可能将这两个函数优化为大致相同,即使不完全相同。
如果您怀疑可以进行一些优化,您应该先 测量 事情 benchmarking/profiling,尽管即使在像这样的简单函数中,您也必须小心很容易得到错误的结果。
如果我们用以下内容替换您的函数:
volatile int Output = 0;
void print_elements1(int * arr, int n)
{
for (int k = 0; k < n; ++k) Output += arr[k];
}
void print_elements2(int * arr, int n)
{
for (int * pa(arr), *pb(arr + n); pa != pb; ++pa) Output += *pa;
}
原因是 printf()
是 "slow",如果我们对您的原始函数进行基准测试,我们实际上只会测试它的速度。使用大小为 1 亿的数组测试这些函数以获得 decent/repeatable 计时,我们得到:
- print_elements1() = 560 毫秒
- print_elements2() = 230 毫秒
啊,我们看到指针访问速度是数组索引的两倍多....但并不快!让我们颠倒测试顺序,看看我们得到了什么:
- print_elements2() = 650 毫秒
- print_elements1() = 260 毫秒
嗯...现在指针访问速度慢了一倍!这是怎么回事?我不完全知道,但它可能必须归因于 CPU/memory 缓存。我们可以尝试通过 运行 基准测试之前的两个函数来消除这种影响:
- print_elements1() = 230 毫秒
- print_elements2() = 230 毫秒
相同的时间,至少在我的基准计时器的误差范围内。
这个故事的寓意是,当今的编译器和计算机都是复杂的机器,您的编译器很可能在优化大多数代码方面比您做得更好。当您进行优化时,首先通过 profiling/benchmarking 进行测量,以确定要处理的代码的最有效区域(如果有)。
What is a good general rule for when to use pointers for arrays versus using access operators?
尽可能始终使用数组索引语法,因为它更具可读性。指针算术语法更难阅读,通常不应该用于迭代。
The second involves 2 variables
源代码中使用的变量数量是对性能和内存消耗的非常糟糕的衡量。实际的机器代码需要将结果存储在某处,无论您是否显式声明变量。如果您声明的变量多于机器代码实际需要的数量,编译器很可能会将它们优化掉。
循环需要知道何时结束迭代。它可以通过在循环中的每一圈在运行时计算 arr+n
来做到这一点(不太可能发生,因为它会很慢)或者它可以通过在启动之前将 arr+n
保存在临时内存位置来做到这一点环形。在第一个示例中,您没有声明这样的变量,因此在实际的机器代码中可能会有一个未命名的变量用于此目的。使两个示例等效。
so that the first one uses fewer operations to iterate through the loop
不是,不是。 C 标准强制 arr[i]
100% 等同于 *(arr + i)
。数组语法只是 "syntactic sugar"。这两种情况极有可能生成相同的机器代码。
(以上等价规则是C允许some weird, ugly crap的原因)
All things considered, which is faster?
由于上述原因,它们同样快。
Is the comparator < faster than != for integral types?
一般应该没什么区别。这一切都归结为在给定 CPU 上可用的汇编程序指令。除了那些与 0 进行比较的比较之外,所有比较方式很可能同样快,它们可能会在某些 CPUs 上为您节省几纳秒。
I want to consider everything possible.
那么我强烈建议您考虑程序的可读性和可维护性,而不是手动微优化。前者造就了当今优秀的程序员,而不是后者。我们不再是 1980 年代了。
正如其他人所指出的,性能问题不会导致我们在访问数组元素时采用一种或另一种表示法。
如果表达同一事物的方式不止一种,请使用更容易理解的方式。
又是品味问题。
我更喜欢索引运算符 []
并执行 a[1],只要我不需要获取数组元素的地址即可。在后一种情况下,我使用 +
运算符,即 a + 1
而不是 &a[1]
.
访问运算符示例:
void print_elements ( int * arr, int n )
{
for (int k = 0; k < n; ++k) printf("%d\n", arr[k]);
}
指针运算例子:
void print_elements ( int * arr, int n )
{
for (int * pa(arr), * pb(arr+n); pa != pb; ++pa) printf("%d\n", *pa);
}
第二个涉及循环的 2 个变量,pa
和 pb
,而第一个只涉及一个额外的变量,k
。再一次,你在做的那一秒
increment pa by 1, dereference
而在第一个你做的
increment k by 1, add k to the pointer arr, derefence
以便第一个使用较少的操作来遍历循环。
综合考虑,哪个更快?对于整数类型,比较器 <
是否比 !=
快?使用 <
似乎 "safer" 因为总是有一种非理性的恐惧,即像 pa != pb
这样的条件作为条件失败,因为如果 pa
可以跳过 pb
如果循环被重构。将指针递增 1
通常比递增大于 1
更快吗?我想考虑一切可能。
这将属于 "premature optimization" 的范围。除非你有特定的(衡量的)理由,否则你通常应该更喜欢最简单和最直接的代码(在这种情况下是你的第一个函数)。现在任何编译器都有可能将这两个函数优化为大致相同,即使不完全相同。
如果您怀疑可以进行一些优化,您应该先 测量 事情 benchmarking/profiling,尽管即使在像这样的简单函数中,您也必须小心很容易得到错误的结果。
如果我们用以下内容替换您的函数:
volatile int Output = 0;
void print_elements1(int * arr, int n)
{
for (int k = 0; k < n; ++k) Output += arr[k];
}
void print_elements2(int * arr, int n)
{
for (int * pa(arr), *pb(arr + n); pa != pb; ++pa) Output += *pa;
}
原因是 printf()
是 "slow",如果我们对您的原始函数进行基准测试,我们实际上只会测试它的速度。使用大小为 1 亿的数组测试这些函数以获得 decent/repeatable 计时,我们得到:
- print_elements1() = 560 毫秒
- print_elements2() = 230 毫秒
啊,我们看到指针访问速度是数组索引的两倍多....但并不快!让我们颠倒测试顺序,看看我们得到了什么:
- print_elements2() = 650 毫秒
- print_elements1() = 260 毫秒
嗯...现在指针访问速度慢了一倍!这是怎么回事?我不完全知道,但它可能必须归因于 CPU/memory 缓存。我们可以尝试通过 运行 基准测试之前的两个函数来消除这种影响:
- print_elements1() = 230 毫秒
- print_elements2() = 230 毫秒
相同的时间,至少在我的基准计时器的误差范围内。
这个故事的寓意是,当今的编译器和计算机都是复杂的机器,您的编译器很可能在优化大多数代码方面比您做得更好。当您进行优化时,首先通过 profiling/benchmarking 进行测量,以确定要处理的代码的最有效区域(如果有)。
What is a good general rule for when to use pointers for arrays versus using access operators?
尽可能始终使用数组索引语法,因为它更具可读性。指针算术语法更难阅读,通常不应该用于迭代。
The second involves 2 variables
源代码中使用的变量数量是对性能和内存消耗的非常糟糕的衡量。实际的机器代码需要将结果存储在某处,无论您是否显式声明变量。如果您声明的变量多于机器代码实际需要的数量,编译器很可能会将它们优化掉。
循环需要知道何时结束迭代。它可以通过在循环中的每一圈在运行时计算 arr+n
来做到这一点(不太可能发生,因为它会很慢)或者它可以通过在启动之前将 arr+n
保存在临时内存位置来做到这一点环形。在第一个示例中,您没有声明这样的变量,因此在实际的机器代码中可能会有一个未命名的变量用于此目的。使两个示例等效。
so that the first one uses fewer operations to iterate through the loop
不是,不是。 C 标准强制 arr[i]
100% 等同于 *(arr + i)
。数组语法只是 "syntactic sugar"。这两种情况极有可能生成相同的机器代码。
(以上等价规则是C允许some weird, ugly crap的原因)
All things considered, which is faster?
由于上述原因,它们同样快。
Is the comparator < faster than != for integral types?
一般应该没什么区别。这一切都归结为在给定 CPU 上可用的汇编程序指令。除了那些与 0 进行比较的比较之外,所有比较方式很可能同样快,它们可能会在某些 CPUs 上为您节省几纳秒。
I want to consider everything possible.
那么我强烈建议您考虑程序的可读性和可维护性,而不是手动微优化。前者造就了当今优秀的程序员,而不是后者。我们不再是 1980 年代了。
正如其他人所指出的,性能问题不会导致我们在访问数组元素时采用一种或另一种表示法。
如果表达同一事物的方式不止一种,请使用更容易理解的方式。
又是品味问题。
我更喜欢索引运算符 []
并执行 a[1],只要我不需要获取数组元素的地址即可。在后一种情况下,我使用 +
运算符,即 a + 1
而不是 &a[1]
.