通过在编译时移位,C++ 基于 1 而不是 0 访问数组
C++ access arrays based on 1 and not 0, by shifting at compile time
我正在写一个小型张量库(灵感来自这里:http://www.wlandry.net/Projects/FTensor),依赖于 C++11 或 14。
为了清楚起见,我想访问从 1 而不是 0 开始的索引。例如,对于向量 v,使用 v(1)、v(2) 和 v(3) 来访问其存储在常规一维数组中的组件。
(编辑:这是针对连续介质力学的应用。人们习惯从1开始。是的,这对程序员来说是不自然的。)
现在,我已经在 运行 的时候知道怎么做了。我想知道我们是否可以在 运行 时避免这种计算,因为在对称的情况下,高阶张量会变得更加复杂。
我发现了这种编写编译时映射的方法,我在其中手动放置了 shift:
但是,使用上面 SO 答案中的代码,我不能这样做:
for i=1; i<4;i++){
std::cout << mymap::get<i>::val << std::endl;
}
因为我不知道编译时间。
有聪明的方法吗?构建一种静态映射,对于每个张量阶数和对称性,我们可以在 运行 时间查找?
谢谢
编辑:这是我的想法 "bad"。 constexpr 实际上没用
// subtract 1 to an index
inline constexpr size_t
sub_1_to_idx(size_t const &i) {
return i - 1;
}
// build a parameter pack from (a,b,c,...) to (a-1,b-1,c-1,...)
template<typename... idx>
constexpr std::array<size_t, sizeof...(idx)>
shift_idx(const idx &... indices) {
return std::array<size_t, sizeof...(idx)>
{{sub_1_to_idx(indices)...}};
};
//use this to return an index of a 1D array where the values of a possibly multidimentional, possibly symmetric Tensor are stored.
template<>
template<typename... idx>
size_t
TA_Index<2, Tsym::IJ>::of(const idx&... indices) {
static_assert(sizeof...(indices) == 2, "*** ERROR *** Please access second order tensor with 2 indices");
const std::array<size_t, sizeof...(idx)> id = shift_idx(indices...);
return id[0] > id[1] ? (id[0] + (id[1] * (2 * 3 - id[1] - 1)) / 2)
: (id[1] + (id[0] * (2 * 3 - id[0] - 1)) / 2);
您对此无能为力。是的,您可以创建一个密集的编译时查找 table,但是您是基于一个(需要填充,即 space 开销)还是基于零(需要重新索引,即时间开销)访问它)?它真的会给你带来任何有意义的表现吗?
您是否比较过基于 1 的索引与基于 0 的索引生成的程序集?
它们实际上是相同的,除了两个 lea
(好吧,从两个输入索引中减去 1)和一个额外的 cmova
(不知道那里发生了什么)。如果从零开始的代码足够好,那么从一开始的代码也同样可以。
此外,在任何一种情况下,编译器都完美地常量折叠了编译时已知的索引。这是编译器的基础优化,因此当索引在编译时已知时,无论您的函数是否 constexpr
都将变得无关紧要。
最后,对于 higher-dimensional/larger 张量的操作,索引计算引起的半随机访问模式可能是比索引计算更大的性能瓶颈(由于没有有效地使用缓存)他们自己。
我正在写一个小型张量库(灵感来自这里:http://www.wlandry.net/Projects/FTensor),依赖于 C++11 或 14。
为了清楚起见,我想访问从 1 而不是 0 开始的索引。例如,对于向量 v,使用 v(1)、v(2) 和 v(3) 来访问其存储在常规一维数组中的组件。 (编辑:这是针对连续介质力学的应用。人们习惯从1开始。是的,这对程序员来说是不自然的。)
现在,我已经在 运行 的时候知道怎么做了。我想知道我们是否可以在 运行 时避免这种计算,因为在对称的情况下,高阶张量会变得更加复杂。
我发现了这种编写编译时映射的方法,我在其中手动放置了 shift:
但是,使用上面 SO 答案中的代码,我不能这样做:
for i=1; i<4;i++){
std::cout << mymap::get<i>::val << std::endl;
}
因为我不知道编译时间。 有聪明的方法吗?构建一种静态映射,对于每个张量阶数和对称性,我们可以在 运行 时间查找?
谢谢
编辑:这是我的想法 "bad"。 constexpr 实际上没用
// subtract 1 to an index
inline constexpr size_t
sub_1_to_idx(size_t const &i) {
return i - 1;
}
// build a parameter pack from (a,b,c,...) to (a-1,b-1,c-1,...)
template<typename... idx>
constexpr std::array<size_t, sizeof...(idx)>
shift_idx(const idx &... indices) {
return std::array<size_t, sizeof...(idx)>
{{sub_1_to_idx(indices)...}};
};
//use this to return an index of a 1D array where the values of a possibly multidimentional, possibly symmetric Tensor are stored.
template<>
template<typename... idx>
size_t
TA_Index<2, Tsym::IJ>::of(const idx&... indices) {
static_assert(sizeof...(indices) == 2, "*** ERROR *** Please access second order tensor with 2 indices");
const std::array<size_t, sizeof...(idx)> id = shift_idx(indices...);
return id[0] > id[1] ? (id[0] + (id[1] * (2 * 3 - id[1] - 1)) / 2)
: (id[1] + (id[0] * (2 * 3 - id[0] - 1)) / 2);
您对此无能为力。是的,您可以创建一个密集的编译时查找 table,但是您是基于一个(需要填充,即 space 开销)还是基于零(需要重新索引,即时间开销)访问它)?它真的会给你带来任何有意义的表现吗?
您是否比较过基于 1 的索引与基于 0 的索引生成的程序集?
它们实际上是相同的,除了两个 lea
(好吧,从两个输入索引中减去 1)和一个额外的 cmova
(不知道那里发生了什么)。如果从零开始的代码足够好,那么从一开始的代码也同样可以。
此外,在任何一种情况下,编译器都完美地常量折叠了编译时已知的索引。这是编译器的基础优化,因此当索引在编译时已知时,无论您的函数是否 constexpr
都将变得无关紧要。
最后,对于 higher-dimensional/larger 张量的操作,索引计算引起的半随机访问模式可能是比索引计算更大的性能瓶颈(由于没有有效地使用缓存)他们自己。