为什么 GCC -O3 在 std::deque 上使用过滤器迭代器导致无限 std::distance?
Why does GCC -O3 cause infinite std::distance with filter iterators over a std::deque?
在经历了很多痛苦和苦难之后,我追踪到了一些非常奇怪的行为,其中 std::distance
从来没有 returns 当 boost::filter_iterator
的范围超过 std::deque
.看来这个问题是 GCC (6.1+) 独有的 -O3
优化。这是一个演示违规行为的示例:
#include <string>
#include <deque>
#include <iterator>
#include <iostream>
#include <boost/iterator/filter_iterator.hpp>
struct Foo
{
std::string bar, s = "";
char a = '[=12=]';
};
int main()
{
const std::deque<Foo> foos(14, {""});
const std::string test {};
const auto p = [test] (const auto& foo) { return foo.bar == test; };
using boost::make_filter_iterator;
const auto begin = make_filter_iterator(p, std::cbegin(foos), std::cend(foos));
const auto end = make_filter_iterator(p, std::cend(foos), std::cend(foos));
std::cout << std::distance(begin, end) << std::endl;
}
一些观察:
- GCC 优化
-O2
或低于预期 returns。
- Clang (3.8) returns 任何优化级别的正确答案。
- 将
std::deque
更改为 std::vector
或 std::list
会产生预期的行为。
14
很关键;少一点,问题就消失了。
sizeof(Foo)
很重要;删除 s
或 a
会使问题消失。
- 通过引用捕获
test
,或仅与常量表达式(例如 foo.bar == " "
)进行比较会导致正常行为。
- 没有编译器警告(
-Wall -Wextra -pedantic
)。
- Valgrind 报告没有错误。
- 使用
fsanitize=undefined
问题就消失了。
怎么回事?
我认为下面的这些发现可能有助于改进错误报告,也有助于在您的代码中使用它来解决问题。
通过调试优化输出并使用优化标志和少量代码更改,我得出了关于导致错误的特定优化标志的结论。
选项集是:
-O -fno-auto-inc-dec -fno-branch-count-reg -fno-combine-stack-adjustments -fno-compare-elim -fno-cprop-registers -fno-dce -fno-defer-pop -fno-delayed-branch -fno-dse -fno-forward-propagate -fno-guess-branch-probability -fno-if-conversion2 -fno-if-conversion -fno-inline-functions-called-once -fno-ipa-pure-const -fno-ipa-profile -fno-ipa-reference -fno-merge-constants -fno-move-loop-invariants -fno-reorder-blocks -fno-shrink-wrap -fno-split-wide-types -fno-ssa-backprop -fno-ssa-phiopt -fno-tree-bit-ccp -fno-tree-ccp -fno-tree-ch -fno-tree-coalesce-vars -fno-tree-phiprop -fno-tree-sink -fno-tree-slsr -fno-tree-dse -fno-tree-forwprop -fno-tree-fre -fno-unit-at-a-time -fno-tree-ter -fno-tree-sra -fno-tree-copy-prop -fstrict-aliasing -ftree-slp-vectorize -std=c++14
抱歉这么长的设置,但我真正想要的是:-O0 -ftree-copy-prop -ftree-pta -ftree-dce -fstrict-aliasing -ftree-slp-vectorize
(我也尝试过 -Og)加上 O1 的神奇步骤...
请注意,-O3 -f-no-tree-slp-vectorize
已经修复了该行为,但是通过使用我发送的完整选项,调试几乎很容易...
此外,运算符 ==(string, string)
的存在似乎在编译器中造成了混乱。
如果您检查下面粘贴的代码,其中所有由 #if 0 代码注释的代码,当激活后在原始代码的位置工作时,您可能会发现我没有的问题。
请注意,甚至没有调用 ==()
运算符,因为 foo.a != '[=16=]'
在测试中始终为真。因此看起来它的存在使编译器生成错误代码。
另请注意,循环内的任何注释代码也会将行为更改为预期的行为,这就是为什么我怀疑初学者的矢量化标志。
#include <string>
#include <deque>
#include <iterator>
#include <iostream>
#include <boost/iterator/filter_iterator.hpp>
#include <string.h>
struct Foo
{
std::string bar, s = "";
char a = 'n';
};
std::ostream& operator<<(std::ostream& os, const Foo& f)
{
os << f.bar << '/' << f.a;
return os;
}
int main()
{
std::deque<Foo> foos(14, {"abc"});
const std::string test {"abc"};
Foo other;
other.bar = "last"; other.a = 'l';
foos.push_back(other);
other.bar = "first";
other.a = 'f';
foos.push_front(other);
//
#if 0
const auto p = [test] (const auto& foo) { return foo.a != '[=11=]'; };
#elif 0
const auto p = [test] (const auto& foo) {
bool rc = (foo.a != '[=11=]');
if (!rc)
rc = (foo.bar == std::string(test));
return rc;
};
#elif 1
const auto p = [test] (const auto& foo) {
bool rc = (foo.a != '[=11=]');
if (!rc)
rc = (foo.bar == test);
return rc;
};
#endif
using boost::make_filter_iterator;
const auto begin = make_filter_iterator(p, std::cbegin(foos), std::cend(foos));
const auto end = make_filter_iterator(p, std::cend(foos), std::cend(foos));
std::cout << std::distance(end, end) << std::endl;
std::cout << std::distance(begin, begin) << std::endl;
std::cout << std::distance(std::cbegin(foos), std::cend(foos)) << std::endl;
auto __first = begin;
auto __last = end;
int __n = 0;
//std::cout << __last << std::endl;
//std::deque<char> trace;
//Foo trace[21];
const int max = foos.size();
char trace[max+5]; memset(trace, 'c', sizeof(trace));
std::cout << max << std::endl;
std::cout << *__last << std::endl;
while (__first != __last)
{
trace[__n] = (*__first).a;
//trace[__n] = (*__first);
//trace.push_back((*__first).a);
//std::cout << *__first << std::endl;
++__n;
++__first;
if (__n > max + 5)
break;
//std::cout << __n << std::endl;
//std::cout << (__first != __last) << std::endl;
}
for (auto f: trace)
std::cout << f << std::endl;
std::cout << "Tadaaaaa: " << __n << std::endl;
//std::cout << std::distance(begin, end) << std::endl;
}
此行为是由于 GCC bug 向量化优化不当造成的。现在已经发布了修复程序,应该会出现在 GCC 6.3 中。
对于那些坚持使用 GCC 5.4 - 6.2 的用户,编译器选项 -fno-tree-slp-vectorize
将 'fix' 问题。
在经历了很多痛苦和苦难之后,我追踪到了一些非常奇怪的行为,其中 std::distance
从来没有 returns 当 boost::filter_iterator
的范围超过 std::deque
.看来这个问题是 GCC (6.1+) 独有的 -O3
优化。这是一个演示违规行为的示例:
#include <string>
#include <deque>
#include <iterator>
#include <iostream>
#include <boost/iterator/filter_iterator.hpp>
struct Foo
{
std::string bar, s = "";
char a = '[=12=]';
};
int main()
{
const std::deque<Foo> foos(14, {""});
const std::string test {};
const auto p = [test] (const auto& foo) { return foo.bar == test; };
using boost::make_filter_iterator;
const auto begin = make_filter_iterator(p, std::cbegin(foos), std::cend(foos));
const auto end = make_filter_iterator(p, std::cend(foos), std::cend(foos));
std::cout << std::distance(begin, end) << std::endl;
}
一些观察:
- GCC 优化
-O2
或低于预期 returns。 - Clang (3.8) returns 任何优化级别的正确答案。
- 将
std::deque
更改为std::vector
或std::list
会产生预期的行为。 14
很关键;少一点,问题就消失了。sizeof(Foo)
很重要;删除s
或a
会使问题消失。- 通过引用捕获
test
,或仅与常量表达式(例如foo.bar == " "
)进行比较会导致正常行为。 - 没有编译器警告(
-Wall -Wextra -pedantic
)。 - Valgrind 报告没有错误。
- 使用
fsanitize=undefined
问题就消失了。
怎么回事?
我认为下面的这些发现可能有助于改进错误报告,也有助于在您的代码中使用它来解决问题。
通过调试优化输出并使用优化标志和少量代码更改,我得出了关于导致错误的特定优化标志的结论。
选项集是:
-O -fno-auto-inc-dec -fno-branch-count-reg -fno-combine-stack-adjustments -fno-compare-elim -fno-cprop-registers -fno-dce -fno-defer-pop -fno-delayed-branch -fno-dse -fno-forward-propagate -fno-guess-branch-probability -fno-if-conversion2 -fno-if-conversion -fno-inline-functions-called-once -fno-ipa-pure-const -fno-ipa-profile -fno-ipa-reference -fno-merge-constants -fno-move-loop-invariants -fno-reorder-blocks -fno-shrink-wrap -fno-split-wide-types -fno-ssa-backprop -fno-ssa-phiopt -fno-tree-bit-ccp -fno-tree-ccp -fno-tree-ch -fno-tree-coalesce-vars -fno-tree-phiprop -fno-tree-sink -fno-tree-slsr -fno-tree-dse -fno-tree-forwprop -fno-tree-fre -fno-unit-at-a-time -fno-tree-ter -fno-tree-sra -fno-tree-copy-prop -fstrict-aliasing -ftree-slp-vectorize -std=c++14
抱歉这么长的设置,但我真正想要的是:-O0 -ftree-copy-prop -ftree-pta -ftree-dce -fstrict-aliasing -ftree-slp-vectorize
(我也尝试过 -Og)加上 O1 的神奇步骤...
请注意,-O3 -f-no-tree-slp-vectorize
已经修复了该行为,但是通过使用我发送的完整选项,调试几乎很容易...
此外,运算符 ==(string, string)
的存在似乎在编译器中造成了混乱。
如果您检查下面粘贴的代码,其中所有由 #if 0 代码注释的代码,当激活后在原始代码的位置工作时,您可能会发现我没有的问题。
请注意,甚至没有调用 ==()
运算符,因为 foo.a != '[=16=]'
在测试中始终为真。因此看起来它的存在使编译器生成错误代码。
另请注意,循环内的任何注释代码也会将行为更改为预期的行为,这就是为什么我怀疑初学者的矢量化标志。
#include <string>
#include <deque>
#include <iterator>
#include <iostream>
#include <boost/iterator/filter_iterator.hpp>
#include <string.h>
struct Foo
{
std::string bar, s = "";
char a = 'n';
};
std::ostream& operator<<(std::ostream& os, const Foo& f)
{
os << f.bar << '/' << f.a;
return os;
}
int main()
{
std::deque<Foo> foos(14, {"abc"});
const std::string test {"abc"};
Foo other;
other.bar = "last"; other.a = 'l';
foos.push_back(other);
other.bar = "first";
other.a = 'f';
foos.push_front(other);
//
#if 0
const auto p = [test] (const auto& foo) { return foo.a != '[=11=]'; };
#elif 0
const auto p = [test] (const auto& foo) {
bool rc = (foo.a != '[=11=]');
if (!rc)
rc = (foo.bar == std::string(test));
return rc;
};
#elif 1
const auto p = [test] (const auto& foo) {
bool rc = (foo.a != '[=11=]');
if (!rc)
rc = (foo.bar == test);
return rc;
};
#endif
using boost::make_filter_iterator;
const auto begin = make_filter_iterator(p, std::cbegin(foos), std::cend(foos));
const auto end = make_filter_iterator(p, std::cend(foos), std::cend(foos));
std::cout << std::distance(end, end) << std::endl;
std::cout << std::distance(begin, begin) << std::endl;
std::cout << std::distance(std::cbegin(foos), std::cend(foos)) << std::endl;
auto __first = begin;
auto __last = end;
int __n = 0;
//std::cout << __last << std::endl;
//std::deque<char> trace;
//Foo trace[21];
const int max = foos.size();
char trace[max+5]; memset(trace, 'c', sizeof(trace));
std::cout << max << std::endl;
std::cout << *__last << std::endl;
while (__first != __last)
{
trace[__n] = (*__first).a;
//trace[__n] = (*__first);
//trace.push_back((*__first).a);
//std::cout << *__first << std::endl;
++__n;
++__first;
if (__n > max + 5)
break;
//std::cout << __n << std::endl;
//std::cout << (__first != __last) << std::endl;
}
for (auto f: trace)
std::cout << f << std::endl;
std::cout << "Tadaaaaa: " << __n << std::endl;
//std::cout << std::distance(begin, end) << std::endl;
}
此行为是由于 GCC bug 向量化优化不当造成的。现在已经发布了修复程序,应该会出现在 GCC 6.3 中。
对于那些坚持使用 GCC 5.4 - 6.2 的用户,编译器选项 -fno-tree-slp-vectorize
将 'fix' 问题。