使用std::map是否应该是确定性的

Using std::map should be deterministic or not?

本文关键字:确定性 是否 std map 使用      更新时间:2023-10-16

我在使用英特尔C++编译器2019更新5时遇到了一个奇怪的行为。当我填充std::map时,它似乎会导致不确定性(?(结果。stl来自VS2019 16.1.6,其中嵌入了ICC。我使用的是Windows 10.0.17134.286。

我的代码:

#include <map>
#include <vector>
#include <iostream>
std::map<int, int> AddToMapWithDependencyBetweenElementsInLoop(const std::vector<int>& values)
{
std::map<int, int>  myMap;
for (int i = 0; i < values.size(); i+=3)
{
myMap.insert(std::make_pair(values[i], myMap.size()));
myMap.insert(std::make_pair(values[i + 1], myMap.size()));
myMap.insert(std::make_pair(values[i + 2], myMap.size()));
}
return myMap;
}
std::map<int, int> AddToMapOnePerLoop(const std::vector<int>& values)
{
std::map<int, int>  myMap;
for (int i = 0; i < values.size(); ++i)
{
myMap.insert(std::make_pair(values[i], 0));
}
return myMap;
}
int main()
{
std::vector<int> values{ 6, 7,  15, 5,  4,  12, 13, 16, 11, 10, 9,  14, 0,  1,  2,  3,  8,  17 };
{
auto myMap = AddToMapWithDependencyBetweenElementsInLoop(values);
for (const auto& keyValuePair : myMap)
{
std::cout << keyValuePair.first << ", ";
}
std::cout << std::endl;
}
{
auto myMap = AddToMapOnePerLoop(values);
for (const auto& keyValuePair : myMap)
{
std::cout << keyValuePair.first << ", ";
}
std::cout << std::endl;
}
return 0;
}

我只是想执行一个测试,所以我从命令行直接调用icl

$ icl /nologo mycode.cpp
$ mycode.exe
0, 1, 2, 3, 4, 5, 6, 7, 11, 12, 13, 14, 15, 16, 17,
0, 1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16, 17

好奇。我期望有18个条目,得到了15个和14个(根据插入方法,请参阅代码(。

$ icl /nologo /EHsc mycode.cpp
$ mycode.exe
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17,
0, 1, 2, 3, 4, 5, 6, 7, 12, 13, 14, 15, 16, 17

仍然很好奇,现在我得到了17和14个参赛作品,而不是18和18个!

$ icl /nologo /Od mycode.cpp
$ mycode.exe
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,

现在,在没有优化的情况下,我得到了18/18,正如预期的那样。

我的问题有两个:1(得到这样的结果正常吗?2(如果不是(我怀疑的(我做错了什么?我认为对编译器的简单调用会正确调用std::map::insert()函数吗?

问题出在for(){}上吗???

感谢您帮助我理解这个问题并找到解决方案!

我无法重现这一点,但在任何一种情况下,为了安心,您都可以更简单地填充地图:

for (auto i: values) {
myMap[i] = 0;
}

不需要仅使用myMap.insert(std::make_pair(key, value))来向映射添加条目。

否则,如果在Ubuntu下使用gcc 8.4.0编译,您的代码会产生预期的输出(0、1、2、3、4、5、6、7、8、9、10、11、12、13、14、15、16、17两次,序列显然是排序的,因为这是有序映射(。我怀疑这只是您使用的特定编译器的一个错误。将错误报告给编译器开发人员,这样他们就可以修复它,这将是有益的

从语法上讲,您的代码很好。我在这里没有看到任何可能的未定义行为(只要你没有进一步隐藏疯狂的黑客行为,比如重新定义size_t/map,修改标准头等(

但是:

由于像这样的一行,我在旧编译器中遇到了循环优化器问题

for (int i = 0; i < values.size(); ++i)

在混合了有符号和无符号整数/数据类型范围的情况下,我怀疑您的intel编译器可能存在循环展开问题。也许这也是由于循环内部的相应问题和下标运算符的使用。这里的典型基本问题:关于允许的寄存器使用的错误假设。你能在这里使用严格的size_t再次尝试你的代码吗?

进一步的想法:

如果要打印的"静态"预定义值是以非常动态的方式创建的,而不是硬代码构建的,那么你能重现这个问题吗?如果你做不到的话,这至少可以排除很多潜在的原因。

只是猜测可能存在与for(...; i+=3)相关的优化

我看到你的用例中的项目数可以除以3,但无论如何,我会为更常见的情况修复你代码中的一个错误:

{
std::map<int, int>  myMap;
for (int i = 0; (i + 2) < values.size(); i+=3)  // ignore the possibly incomplete last triplet

我知道这与您的问题没有直接关系,但也许这个修复程序会触发编译器优化器中的某些东西来构建正确的代码。