是否可以微优化 "x = max(a,b); y = min(a,b);"?
Is it possible to micro-optimize "x = max(a,b); y = min(a,b);"?
我的算法一开始就像
int sumLargest2 ( int * arr, size_t n )
{
int largest(max(arr[0], arr[1])), secondLargest(min(arr[0],arr[1]));
// ...
我意识到第一个可能不是最优的,因为调用 max
然后 min
是重复的,当你认为知道最小值所需的信息已经存在时,一旦你找到最大值。所以我发现我可以做
int largest = max(arr[0], arr[1]);
int secondLargest = arr[0] == largest ? arr[1] : arr[0];
减少无用的 min
调用,但我不确定是否真的节省了多少操作。是否有任何奇特的位移算法可以做相当于
int largest(max(arr[0], arr[1])), secondLargest(min(arr[0],arr[1]));
??????
怎么样:
int largestIndex = arr[1] > arr[0];
int largest = arr[largestIndex];
int secondLargest = arr[1 - largestIndex];
第一行依赖于布尔结果在 true 的情况下隐式转换为 1,在 false 的情况下转换为 0。
在 C++ 中,您可以使用 std::minmax
to produce a std::pair
of the minimum and the maximum. This is particularly easy in combination with std::tie
:
#include <algorithm>
#include <utility>
int largest, secondLargest;
std::tie(secondLargest, largest) = std::minmax(arr[0], arr[1]);
GCC 至少能够将对 minmax 的调用优化为单个比较,与下面的 C 代码的结果相同。
在 C 中,您可以自己编写测试:
int largest, secondLargest;
if (arr[0] < arr[1]) {
largest = arr[1];
secondLargest = arr[0];
} else {
largest = arr[0];
secondLargest = arr[1];
}
如果您的目的是减少查找 min mad max 的函数调用,您可以尝试 std::minmax_element
。自 C++11 起可用。
auto result = std::minmax_element(arr, arr+n);
std::cout<< "min:"<< *result.first<<"\n";
std::cout<< "max :" <<*result.second << "\n";
如果您只想找到两个值中较大的一个,请执行以下操作:
if(a > b)
{
largest = a;
second = b;
}
else
{
largest = b;
second = a;
}
没有函数调用,一次比较,两次赋值。
我假设您更愿意解决更大的问题...也就是说,获取数组中最大的两个数字的总和。
您要做的是 std::partial_sort()
。
让我们来实施吧。
int sumLargest2(int * arr, size_t n) {
int * first = arr;
int * middle = arr + 2;
int * last = arr + n;
std::partial_sort(first, middle, last, std::greater<int>());
return arr[0] + arr[1];
}
如果您无法修改 arr
,那么我建议您查看 std::partial_sort_copy()
。
时间-space权衡如何?
#include <utility>
template<typename T>
std::pair<T, T>
minmax(T const& a, T const& b)
{ return b < a ? std::make_pair(b, a) : std::make_pair(a, b); }
//main
std::pair<int, int> values = minmax(a[0], a[1]);
int largest = values.second;
int secondLargest = values.first;
x = max(a, b);
y = a + b - x;
不一定会更快,但会有所不同。
还要注意溢出。
我假设是 C++...
简答,使用std::minmax并使用正确的优化和正确的指令集参数进行编译。
冗长丑陋的答案,编译器无法做出所有必要的假设来使其非常非常快。你可以。在这种情况下,您可以更改算法以先处理所有数据,然后可以强制对数据进行对齐。完成所有这些操作后,您可以使用内部函数使其更快。
尽管我没有在这个特定案例中对其进行测试,但我已经看到使用这些准则可以显着提高性能。
由于您没有将 2 个整数传递给函数,我假设您使用的是数组并想以某种方式迭代它。您现在可以做出选择:制作 2 个数组并使用 min/max 或使用 1 个数组同时使用 a
和 b
。单单这个决定就已经可以影响性能了。
如果您有 2 个数组,可以使用对齐的 malloc 在 32 字节边界上分配这些数组,然后使用内部函数进行处理。如果您想要真正的原始性能 - 这就是您要走的路。
F.ex,假设您有 AVX2。 (注意:我不确定你是否这样做,你应该使用 CPU id 来检查它!)。转到此处的作弊 sheet:https://software.intel.com/sites/landingpage/IntrinsicsGuide/ 并选择你的毒药。
在这种情况下,您要查找的内在函数可能是:
- _mm256_min_epi32
- _mm256_max_epi32
- _mm256_stream_load_si256
如果您必须对整个数组执行此操作,您可能希望在合并各个项目之前将所有内容保存在一个 __mm256 寄存器中。例如:对每个 256 位向量执行 min/max,当循环完成后,提取 32 位项并对其执行 min/max。
更好的答案:所以...至于编译器。编译器确实尝试优化这类事情,但 运行 遇到了问题。
如果您处理 2 个不同的数组,编译器必须知道它们是不同的才能对其进行优化。这就是为什么像 restrict 这样的东西存在的原因,它告诉编译器你在编写代码时可能已经知道的这个小东西。
另外,编译器不知道你的内存是对齐的,所以它必须检查这个和分支...对于每个调用。我们不想要这个;这意味着我们希望它内联它的东西。所以,添加 inline
,将其放入头文件中,仅此而已。你也可以用aligned
给他提示。
您的编译器也没有得到 int*
不会随时间变化的提示。如果无法更改,最好使用 const
关键字告诉他。
编译器使用指令集进行编译。通常,他们已经在使用 SSE,但 AVX2 可以提供很多帮助(正如我在上面的内在函数中所展示的那样)。如果你可以用这些标志编译它,一定要使用它们——它们很有帮助。
运行 在发布模式下,在 'fast' 上进行优化编译,看看幕后发生了什么。如果执行所有这些操作,您应该会看到 vpmax...
指令出现在内部循环中,这意味着编译器可以很好地使用内部函数。
我不知道你还想在循环中做什么...如果你使用所有这些指令,你应该在大数组上达到内存速度。
我的算法一开始就像
int sumLargest2 ( int * arr, size_t n )
{
int largest(max(arr[0], arr[1])), secondLargest(min(arr[0],arr[1]));
// ...
我意识到第一个可能不是最优的,因为调用 max
然后 min
是重复的,当你认为知道最小值所需的信息已经存在时,一旦你找到最大值。所以我发现我可以做
int largest = max(arr[0], arr[1]);
int secondLargest = arr[0] == largest ? arr[1] : arr[0];
减少无用的 min
调用,但我不确定是否真的节省了多少操作。是否有任何奇特的位移算法可以做相当于
int largest(max(arr[0], arr[1])), secondLargest(min(arr[0],arr[1]));
??????
怎么样:
int largestIndex = arr[1] > arr[0];
int largest = arr[largestIndex];
int secondLargest = arr[1 - largestIndex];
第一行依赖于布尔结果在 true 的情况下隐式转换为 1,在 false 的情况下转换为 0。
在 C++ 中,您可以使用 std::minmax
to produce a std::pair
of the minimum and the maximum. This is particularly easy in combination with std::tie
:
#include <algorithm>
#include <utility>
int largest, secondLargest;
std::tie(secondLargest, largest) = std::minmax(arr[0], arr[1]);
GCC 至少能够将对 minmax 的调用优化为单个比较,与下面的 C 代码的结果相同。
在 C 中,您可以自己编写测试:
int largest, secondLargest;
if (arr[0] < arr[1]) {
largest = arr[1];
secondLargest = arr[0];
} else {
largest = arr[0];
secondLargest = arr[1];
}
如果您的目的是减少查找 min mad max 的函数调用,您可以尝试 std::minmax_element
。自 C++11 起可用。
auto result = std::minmax_element(arr, arr+n);
std::cout<< "min:"<< *result.first<<"\n";
std::cout<< "max :" <<*result.second << "\n";
如果您只想找到两个值中较大的一个,请执行以下操作:
if(a > b)
{
largest = a;
second = b;
}
else
{
largest = b;
second = a;
}
没有函数调用,一次比较,两次赋值。
我假设您更愿意解决更大的问题...也就是说,获取数组中最大的两个数字的总和。
您要做的是 std::partial_sort()
。
让我们来实施吧。
int sumLargest2(int * arr, size_t n) {
int * first = arr;
int * middle = arr + 2;
int * last = arr + n;
std::partial_sort(first, middle, last, std::greater<int>());
return arr[0] + arr[1];
}
如果您无法修改 arr
,那么我建议您查看 std::partial_sort_copy()
。
时间-space权衡如何?
#include <utility>
template<typename T>
std::pair<T, T>
minmax(T const& a, T const& b)
{ return b < a ? std::make_pair(b, a) : std::make_pair(a, b); }
//main
std::pair<int, int> values = minmax(a[0], a[1]);
int largest = values.second;
int secondLargest = values.first;
x = max(a, b);
y = a + b - x;
不一定会更快,但会有所不同。
还要注意溢出。
我假设是 C++...
简答,使用std::minmax并使用正确的优化和正确的指令集参数进行编译。
冗长丑陋的答案,编译器无法做出所有必要的假设来使其非常非常快。你可以。在这种情况下,您可以更改算法以先处理所有数据,然后可以强制对数据进行对齐。完成所有这些操作后,您可以使用内部函数使其更快。
尽管我没有在这个特定案例中对其进行测试,但我已经看到使用这些准则可以显着提高性能。
由于您没有将 2 个整数传递给函数,我假设您使用的是数组并想以某种方式迭代它。您现在可以做出选择:制作 2 个数组并使用 min/max 或使用 1 个数组同时使用 a
和 b
。单单这个决定就已经可以影响性能了。
如果您有 2 个数组,可以使用对齐的 malloc 在 32 字节边界上分配这些数组,然后使用内部函数进行处理。如果您想要真正的原始性能 - 这就是您要走的路。
F.ex,假设您有 AVX2。 (注意:我不确定你是否这样做,你应该使用 CPU id 来检查它!)。转到此处的作弊 sheet:https://software.intel.com/sites/landingpage/IntrinsicsGuide/ 并选择你的毒药。
在这种情况下,您要查找的内在函数可能是:
- _mm256_min_epi32
- _mm256_max_epi32
- _mm256_stream_load_si256
如果您必须对整个数组执行此操作,您可能希望在合并各个项目之前将所有内容保存在一个 __mm256 寄存器中。例如:对每个 256 位向量执行 min/max,当循环完成后,提取 32 位项并对其执行 min/max。
更好的答案:所以...至于编译器。编译器确实尝试优化这类事情,但 运行 遇到了问题。
如果您处理 2 个不同的数组,编译器必须知道它们是不同的才能对其进行优化。这就是为什么像 restrict 这样的东西存在的原因,它告诉编译器你在编写代码时可能已经知道的这个小东西。
另外,编译器不知道你的内存是对齐的,所以它必须检查这个和分支...对于每个调用。我们不想要这个;这意味着我们希望它内联它的东西。所以,添加 inline
,将其放入头文件中,仅此而已。你也可以用aligned
给他提示。
您的编译器也没有得到 int*
不会随时间变化的提示。如果无法更改,最好使用 const
关键字告诉他。
编译器使用指令集进行编译。通常,他们已经在使用 SSE,但 AVX2 可以提供很多帮助(正如我在上面的内在函数中所展示的那样)。如果你可以用这些标志编译它,一定要使用它们——它们很有帮助。
运行 在发布模式下,在 'fast' 上进行优化编译,看看幕后发生了什么。如果执行所有这些操作,您应该会看到 vpmax...
指令出现在内部循环中,这意味着编译器可以很好地使用内部函数。
我不知道你还想在循环中做什么...如果你使用所有这些指令,你应该在大数组上达到内存速度。