C++ 在编译时计算和排序向量
C++ calculate and sort vector at compile time
我有一个 class A
有一个 std::vector<int>
作为属性。
A
需要在创建 A
的实例时填充此向量。
计算可能需要一些时间,我想知道是否:
- 可以在编译时完成。
- 向量也可以在编译时排序
我对元编程不熟悉,暂时没有找到方法。这不是 OS 特定的问题。
这是 A.cpp
文件:
#include "A.h"
#define SIZEV 100
A::A()
{
fillVector();
}
void A::fillVector()
{
// m_vector is an attribute of class "A"
// EXPECTATION 1 : fill the vector with the following calculation at compile time
const int a=5;
const int b=7;
const int c=9;
for(int i=0;i<SIZEV;i++){
for(int j=0;j<SIZEV;j++){
for(int k=0;k<SIZEV;k++){
this->m_vector.push_back(a*i+b*j+c*k);
}
}
}
// EXPECTATION 2 : sort the vector as compile time
}
std::vector<int> A::getVector() const
{
return m_vector;
}
void A::setVector(const std::vector<int> &vector)
{
m_vector = vector;
}
和main.cpp
(Qt应用程序但没关系):
#include <QCoreApplication>
#include "A.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
A a;
auto vec = a.getVector();
// use vec, etc ...
return app.exec();
}
A std::vector<int>
没有任何 constexpr
构造函数(因为 constexpr
不允许动态内存分配)。所以你不能在编译时对 std::vector<int>
进行排序。
您可以在编译时为常量 N
创建 std::array<int, N>
,但您必须编写自己的排序例程,因为 std::sort
不是 constexpr
任何一个。
您还可以编写 Boost.MPL 编译时向量或列表并使用其 sort
例程。但这不会像 std::array
.
那样扩展。
另一个攻击角度可能是将向量存储到 static
变量中并在程序初始化时进行排序。您的程序只是需要更长的时间来启动,但不会影响其其余主要功能。
由于排序是 O(N log N)
,您甚至可以进行两步构建并将排序后的向量写入文件,然后 compile/link 将其写入主程序,或将其加载到 O(N)
在程序启动时进入 static
变量。
可预先计算的冗长计算的经典方法是将计算结果作为构建过程的一部分,生成 .cpp
对结果进行硬编码(在具有嵌入式资源的平台上,可以使用这些以及)。 .
不过,这里的计算非常简单,慢的部分大概就是分配,如果你想把数据保持在一个std::vector
,有 在运行时发生。如果您可以使用 C 风格的数组,您可以如上所述将它全部放入可执行文件中,但这会产生一个大 4 MB 的可执行文件,并且从磁盘加载它所导致的减速将抵消预先计算的任何速度优势。
IOW:当计算量大且输出量小时,在构建时进行预计算是有意义的。你的情况与频谱完全相反,所以我会避免它。
也许不完全是您要查找的内容,但您可以编写一个单独的程序来计算向量,对其进行排序,然后将其输出到列表中。然后你就可以读入那个文件了。
如果从磁盘读取太慢,您还可以将输出整理成一个合法的 C++ 文件,该文件初始化您的 class 的实例,该实例具有满足您要求的硬编码值。然后可以将其链接回您的主项目并进行编译,本质上提供与您在此处概述的更复杂的元编程任务相同的功能。
虽然可以这样做 (live example),但您不应该这样做。这将占用大量编译时间。
编译器不是为快速、高效的数字批量处理而设计的。现在,将您的编译时工作限制在相对简单的事情上,而不是对 1000 万个元素进行排序。
即使您编写 "compliant" 代码,今天的大多数编译器也会对您产生影响。我写的代码很早就死了,尽管我试图小心我的递归深度限制。
总之,为子孙后代:
template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;
template<int...Xs> struct values { constexpr values() {}; };
template<int...Xs> constexpr values<Xs...> values_v = {};
template<class...Vs> struct append;
template<class...Vs> using append_t=type_t<append<Vs...>>;
template<class...Vs> constexpr append_t<Vs...> append_v = {};
template<> struct append<>:tag<values<>>{};
template<int...Xs>struct append<values<Xs...>>:tag<values<Xs...>>{};
template<int...Lhs, int...Rhs, class...Vs>
struct append<values<Lhs...>,values<Rhs...>,Vs...>:
tag<append_t<values<Lhs...,Rhs...>,Vs...>>
{};
template<int...Lhs>
constexpr values<Lhs...> simple_merge( values<Lhs...>, values<> ) { return {}; }
template<int...Rhs>
constexpr values<Rhs...> simple_merge( values<>, values<Rhs...> ) { return {}; }
constexpr values<> simple_merge( values<>, values<> ) { return {}; }
template<int L0, int...Lhs, int R0, int...Rhs>
constexpr auto simple_merge( values<L0, Lhs...>, values<R0, Rhs...> )
-> std::conditional_t<
(R0 < L0),
append_t< values<R0>, decltype( simple_merge( values<L0,Lhs...>{}, values<Rhs...>{} ) ) >,
append_t< values<L0>, decltype( simple_merge( values<Lhs...>{}, values<R0, Rhs...>{} ) ) >
> {
return {};
}
template<class Lhs, class Rhs>
using simple_merge_t = decltype( simple_merge( Lhs{}, Rhs{} ) );
template<class Lhs, class Rhs>
constexpr simple_merge_t<Lhs, Rhs> simple_merge_v = {};
template<class Values, size_t I> struct split
{
private:
using one = split<Values, I/2>;
using two = split<typename one::rhs, I-I/2>;
public:
using lhs = append_t< typename one::lhs, typename two::lhs >;
using rhs = typename two::rhs;
};
template<class Values, size_t I> using split_t=type_t<split<Values, I>>;
template<class Values> struct split<Values, 0>{
using lhs = values<>;
using rhs = Values;
};
template<int X0, int...Xs> struct split<values<X0, Xs...>, 1> {
using lhs = values<X0>;
using rhs = values<Xs...>;
};
template<class Values, size_t I> using before_t = typename split<Values, I>::lhs;
template<class Values, size_t I> using after_t = typename split<Values, I>::rhs;
template<size_t I>using index_t=std::integral_constant<size_t, I>;
template<int I>using int_t=std::integral_constant<int, I>;
template<int I>constexpr int_t<I> int_v={};
template<class Values> struct front;
template<int X0, int...Xs> struct front<values<X0, Xs...>>:tag<int_t<X0>>{};
template<class Values> using front_t=type_t<front<Values>>;
template<class Values> constexpr front_t<Values> front_v = {};
template<class Values, size_t I>
struct get:tag<front_t< after_t<Values, I> >> {};
template<class Values, size_t I> using get_t = type_t<get<Values, I>>;
template<class Values, size_t I> constexpr get_t<Values, I> get_v = {};
template<class Values>
struct length;
template<int...Xs>
struct length<values<Xs...>>:tag<index_t<sizeof...(Xs)>> {};
template<class Values> using length_t = type_t<length<Values>>;
template<class Values> constexpr length_t<Values> length_v = {};
template<class Values> using front_half_t = before_t< Values, length_v<Values>/2 >;
template<class Values> constexpr front_half_t<Values> front_half_v = {};
template<class Values> using back_half_t = after_t< Values, length_v<Values>/2 >;
template<class Values> constexpr back_half_t<Values> back_half_v = {};
template<class Lhs, class Rhs>
struct least : tag< std::conditional_t< (Lhs{}<Rhs{}), Lhs, Rhs > > {};
template<class Lhs, class Rhs> using least_t = type_t<least<Lhs, Rhs>>;
template<class Lhs, class Rhs>
struct most : tag< std::conditional_t< (Lhs{}>Rhs{}), Lhs, Rhs > > {};
template<class Lhs, class Rhs> using most_t = type_t<most<Lhs, Rhs>>;
template<class Values>
struct pivot {
private:
using a = get_t<Values, 0>;
using b = get_t<Values, length_v<Values>/2>;
using c = get_t<Values, length_v<Values>-1>;
using d = most_t< least_t<a,b>, most_t< least_t<b,c>, least_t<a,c> > >;
public:
using type = d;
};
template<int X0, int X1>
struct pivot<values<X0, X1>>: tag< most_t< int_t<X0>, int_t<X1> > > {};
template<class Values> using pivot_t = type_t<pivot<Values>>;
template<class Values> constexpr pivot_t<Values> pivot_v = {};
template<int P>
constexpr values<> lower_split( int_t<P>, values<> ) { return {}; }
template<int P, int X0>
constexpr std::conditional_t< (X0<P), values<X0>, values<> > lower_split( int_t<P>, values<X0> ) { return {}; }
template<int P, int X0, int X1, int...Xs >
constexpr auto lower_split( int_t<P>, values<X0, X1, Xs...> )
-> append_t<
decltype(lower_split( int_v<P>, front_half_v<values<X0, X1, Xs...>> )),
decltype(lower_split( int_v<P>, back_half_v<values<X0, X1, Xs...>> ))
>{ return {}; }
template<int P>
constexpr values<> upper_split( int_t<P>, values<> ) { return {}; }
template<int P, int X0>
constexpr std::conditional_t< (X0>P), values<X0>, values<> > upper_split( int_t<P>, values<X0> ) { return {}; }
template<int P, int X0, int X1, int...Xs>
constexpr auto upper_split( int_t<P>, values<X0, X1, Xs...> )
-> append_t<
decltype(upper_split( int_v<P>, front_half_v<values<X0, X1, Xs...>> )),
decltype(upper_split( int_v<P>, back_half_v<values<X0, X1, Xs...>> ))
>{ return {}; }
template<int P>
constexpr values<> middle_split( int_t<P>, values<> ) { return {}; }
template<int P, int X0>
constexpr std::conditional_t< (X0==P), values<X0>, values<> > middle_split( int_t<P>, values<X0> ) { return {}; }
template<int P, int X0, int X1, int...Xs>
constexpr auto middle_split( int_t<P>, values<X0, X1, Xs...> )
-> append_t<
decltype(middle_split( int_v<P>, front_half_v<values<X0, X1, Xs...>> )),
decltype(middle_split( int_v<P>, back_half_v<values<X0, X1, Xs...>> ))
>{ return {}; }
template<class Values>
using lower_split_t = decltype(lower_split( pivot_v<Values>, Values{} ) );
template<class Values> constexpr lower_split_t<Values> lower_split_v = {};
template<class Values>
using upper_split_t = decltype(upper_split( pivot_v<Values>, Values{} ) );
template<class Values> constexpr upper_split_t<Values> upper_split_v = {};
template<class Values>
using middle_split_t = decltype(middle_split( pivot_v<Values>, Values{} ) );
template<class Values> constexpr middle_split_t<Values> middle_split_v = {};
constexpr values<> simple_merge_sort( values<> ) { return {}; }
template<int X>
constexpr values<X> simple_merge_sort( values<X> ) { return {}; }
template<class Values>
using simple_merge_sort_t = decltype( simple_merge_sort( Values{} ) );
template<class Values>
constexpr simple_merge_sort_t<Values> simple_merge_sort_v = {};
template<int X0, int X1, int...Xs>
constexpr auto simple_merge_sort( values<X0, X1, Xs...> )
->
simple_merge_t<
simple_merge_t<
simple_merge_sort_t<lower_split_t<values<X0, X1, Xs...>>>, simple_merge_sort_t<upper_split_t<values<X0, X1, Xs...>>>
>,
middle_split_t<values<X0, X1, Xs...>>
>
{ return {}; }
template<class Values>constexpr Values cross_add( Values ) { return {}; }
template<class Values>constexpr values<> cross_add( values<>, Values ) { return {}; }
template<int A0, int...B>constexpr values<(B+A0)...> cross_add( values<A0>, values<B...> ) { return {}; }
template<int A0, int A1, int...A, int...B>
constexpr auto cross_add( values<A0, A1, A...>, values<B...>)
-> append_t<
decltype(cross_add( front_half_v<values<A0, A1, A...>>, values_v<B...> ) ),
decltype(cross_add( back_half_v<values<A0, A1, A...>>, values_v<B...> ) )
> { return {}; }
template<class V0, class V1, class V2, class... Vs>
constexpr auto cross_add( V0, V1, V2, Vs... )
-> decltype(
cross_add( cross_add( V0{}, V1{} ), V2{}, Vs{}... )
) { return {}; }
template<class...Vs>
using cross_add_t = decltype( cross_add(Vs{}...) );
template<class...Vs>
constexpr cross_add_t<Vs...> cross_add_v = {};
template<int X, int...Xs>
constexpr values<(X*Xs)...> scale( int_t<X>, values<Xs...> ) { return {}; }
template<class X, class Xs>
using scale_t = decltype( scale(X{}, Xs{}) );
template<class X, class Xs> constexpr scale_t<X,Xs> scale_v = {};
template<int X0, int...Xs> struct generate_values : generate_values<X0-1, X0-1, Xs...> {};
template<int...Xs> struct generate_values<0,Xs...>:tag<values<Xs...>>{};
template<int X0> using generate_values_t = type_t<generate_values<X0>>;
三中值编译时合并排序和叉积生成器。通过努力,我可以大大减少行数。
使用 constexpr std::array
可能会比上述纯类型解决方案更快。
数据是0
到SIZEV * (a+b+c)
的整数,但是整数的个数是SIZEV
3。它是一组密集的整数,范围很小,所以 CountingSort 是完美的(你永远不需要构建未排序的数组,只需要在生成时递增计数)。
不管保持计数/前缀总和,CountingSort 绝对会在启动时间上取得巨大胜利,对向量进行排序,与其他排序相比,保持其他一切相同。
您可以将数据的紧凑形式(O(cuberoot(n)) 大小)保留为 prefix sums 的向量,以便从 m_vector 中查找 O(log (cuberoot(n) ))) 时间(二进制搜索前缀和),其中 n 是 m_vector 的长度。见下文。
取决于缓存/内存延迟,不实际扩展 m_vector 可能会也可能不会是性能上的胜利。如果需要一系列值,您可以从前缀和中快速生成 m_vector 的连续元素。
class A {
// vector<uint16_t> m_counts; // needs to be 32b for SIZEV>=794 (found experimentally).
vector<uint32_t> m_pos; // values are huge: indices into m_vector, up to SIZEV**3 - 1
vector<uint16_t> m_vector; // can be 16b until SIZEV>3121: max val is only (a+b+c) * (SIZEV-1)
}
void A::fillVector()
{
const int a=5;
const int b=7;
const int c=9;
const auto max_val = (SIZEV-1) * (a+b+c);
m_vector.reserve(SIZEV*SIZEV*SIZEV);
m_vector.resize(0);
// or clear it, but that writes tons of mem, unless you use a custom Allocator::construct to leave it uninit
// http://en.cppreference.com/w/cpp/container/vector/resize
m_pos.resize(max_val + 1); // again, ideally avoid zeroing
// but if not, do it before m_counts
m_counts.clear(); // do this one last, so it's hot in cache even if others wasted time writing zeros.
m_counts.resize(max_val + 1); // vector is now zeroed
// Optimization: don't have a separate m_counts.
// zero and count into m_pos, then do prefix summing in-place
// manually strength-reduce the multiplication to addition
// in case the compiler decides it won't, or can't prove it won't overflow the same way
// Not necessary with gcc or clang: they both do this already
for(int kc=c*(SIZEV-1) ; kc >= 0 ; kc-=c) {
for(int jb=b*(SIZEV-1) ; jb >= 0 ; jb-=b) {
for(int ia=a*(SIZEV-1) ; ia >= 0 ; ia-=a) {
m_counts[kc + jb + ia]++;
// do the smallest stride in the inner-most loop, for better cache locality
}
}
}
// write the early elements last, so they'll be hot in the cache when we're done
int val = 0;
uint32_t sum = 0;
for ( auto &count : m_counts ) {
m_vector.insert(m_vector.end(), count, val++);
// count is allowed to be zero for vector::insert(pos, count, value)
m_pos[val] = sum; // build our vector of prefix sums
sum += count;
//count = (sum+=count); // in-place conversion to prefix sums
}
assert(m_vector.size() == SIZEV*SIZEV*SIZEV);
}
或者,不是实际扩展 1.6GB 数组,而是计算 Prefix sums,为您提供该索引的 运行 起始位置的向量作为 m_vector
。即 idx = m_pos[val]; m_vector[idx] == val
。 (这分解为 val <= 13,其中有些值不能表示为 a、b 和 c 的总和,因此 m_count
中有零,并在 m_pos
中重复)
无论如何,您可以用 m_pos
中的 i
的二进制搜索替换对 m_vector[i]
的读取。您正在寻找 m_pos
中值 <= i 的最高索引。该索引就是您在 m_vector[i]
中找到的内容。 (或类似的东西;我可能有一个差一的错误。)
散列 table 将不起作用,因为您需要将 i
的多个值映射到 0..(750*(a+b+c)) 中的每个数字。 (m_vector[i]
具有相同值的所有 i
。)
如果您需要 运行 个顺序元素,请将它们即时生成到 tmp 缓冲区中。查看 m_pos[i+1]
以查看下一个具有不同值的元素何时到来。 (查看 m_counts
可能会节省一些减法,但您最好只取 m_pos
中的差异来反转前缀和,以避免缓存未命中/缓存污染接触第二个数组。)
实际上,m_counts
可能根本不需要作为 class 成员保留,只是 FillVector 中的临时成员。或者 FillVector 可以算入 m_pos
,并将其就地转换为前缀和。
理想情况下,您可以使用模板做一些聪明的事情,为 m_counts 和 m_vector 选择足够宽但不超过需要的类型。 IDK数论,所以我不知道如何证明不会有一桶m_counts
溢出一个uint16_t
。 平均 计数将为 750**3 / (750*(5+7+9)) = 26786,并且它们肯定聚集在 m_counts
的高端.在实践中,SIZEV=793 可以使用 uint16_t 计数器,而 SIZEV=794 produces several counts > 65536 (感谢 Chris 提供的工作示例,我可以在其中轻松测试它)。
m_vector
可以是 uint16_t
直到 (SIZEV-1)*(a+b+c) > MAX_UINT16
(65535)。即直到 SIZEV >= 3122,此时 m_vector
占用 28.3 GiB 的 RAM。
在 SIZEV = 750 时,m_pos
大约是 L1 缓存大小的 2 倍(Intel CPU)(750*(5+7+9) * 4B per short = 63000B
)。如果编译器做得很好并使用条件移动而不是 unpredictable 分支指令进行二进制搜索,这可能会非常快。它肯定会为您节省大量的主内存流量,如果您有多个线程,这很有价值。
或者,永远不要触及 m_vector
意味着您可以 处理问题大小,这需要比您 存储列表更多的内存。
如果您想在首先创建 m_counts 时(使用三重嵌套循环)通过缓存优化获得真正的创意,请让最内层的循环向前然后向后,而不是相同方向两次。这只会影响非常大的 SIZEV,或者如果另一个超线程对缓存施加很大压力。
for(int kc=c*(SIZEV-1) ; kc >= 0 ; kc-=c) {
for(int jb=b*(SIZEV-1) ; jb >= 0 ; jb-=b) {
for(int ia=0 ; ia<SIZEV*a ; ia+=a)
counts[kc + jb + ia]++;
if (! (jb-=b )) break;
for(int ia=a*(SIZEV-1) ; ia >= 0 ; ia-=a)
counts[kc + jb + ia]++;
}
}
向零倒计时(有或没有双向内部循环)很可能是下一个循环开始的小胜利,在计数变高时执行大 memsets 成为内存限制之前。扫描前向以就地进行前缀和也是一个胜利。
我之前的回答,可能是死路一条:
是否有希望找到排序向量中第 i
个元素的闭式公式?或者甚至是用于动态生成它的 O(log i) 算法?
除非您在访问该向量时需要大量顺序元素,否则可能动态计算它会更快。内存很慢,CPU 很快,所以如果你能在大约 150 个时钟周期内计算出 a[i]
,你就领先了。 (假设每次访问都是缓存未命中,或者不接触所有向量内存会减少程序其余部分的缓存未命中)。
如果我们能做到这一点,理论上我们可以首先按顺序编写排序数组。
要做到这一点:打乱常量 a <= b <= c
。
0, a, [a*2 .. a*int(b/a)], b, [b + a .. b + a*int((c-b)/a) mixed with b*2 .. b*int(c/b)], c, [some number of b*x + a*y], c+a, [more b*x + a*y], ...
好的,所以这变成了组合混乱,这个想法可能不可行。至少,不是对于任何 a、b 和 c 的一般情况。
a=5,b=7,c=9:
0, 5=a, 7=b, 9=c, 10=2a, 12=b+a, 14=2b, 14=c+a, 15=3a, 16=c+b, 18 =2c
我太困了,看不到模式,但这里有一个更长的列表
# bash
limit=5; for ((i=0 ; i<limit ; i++)); do
for ((j=0 ; j<limit ; j++)); do
for ((k=0 ; k<limit ; k++)); do
printf "%2d: %d %d %d\n" $((5*i + 7*j + 9*k)) $i $j $k;
done; done; done | sort -n | cat -n
1 0: 0 0 0
2 5: 1 0 0
3 7: 0 1 0
4 9: 0 0 1
5 10: 2 0 0
6 12: 1 1 0
7 14: 0 2 0
8 14: 1 0 1
9 15: 3 0 0
10 16: 0 1 1
11 17: 2 1 0
12 18: 0 0 2
13 19: 1 2 0
14 19: 2 0 1
15 20: 4 0 0
16 21: 0 3 0
17 21: 1 1 1
18 22: 3 1 0
19 23: 0 2 1
20 23: 1 0 2
21 24: 2 2 0
22 24: 3 0 1
23 25: 0 1 2
24 26: 1 3 0
25 26: 2 1 1
26 27: 0 0 3
27 27: 4 1 0
28 28: 0 4 0
29 28: 1 2 1
30 28: 2 0 2
31 29: 3 2 0
32 29: 4 0 1
33 30: 0 3 1
34 30: 1 1 2
35 31: 2 3 0
36 31: 3 1 1
37 32: 0 2 2
38 32: 1 0 3
39 33: 1 4 0
40 33: 2 2 1
41 33: 3 0 2
42 34: 0 1 3
43 34: 4 2 0
44 35: 1 3 1
45 35: 2 1 2
46 36: 0 0 4
47 36: 3 3 0
48 36: 4 1 1
49 37: 0 4 1
50 37: 1 2 2
51 37: 2 0 3
52 38: 2 4 0
53 38: 3 2 1
54 38: 4 0 2
55 39: 0 3 2
56 39: 1 1 3
57 40: 2 3 1
58 40: 3 1 2
59 41: 0 2 3
60 41: 1 0 4
61 41: 4 3 0
62 42: 1 4 1
63 42: 2 2 2
64 42: 3 0 3
65 43: 0 1 4
66 43: 3 4 0
67 43: 4 2 1
68 44: 1 3 2
69 44: 2 1 3
70 45: 3 3 1
71 45: 4 1 2
72 46: 0 4 2
73 46: 1 2 3
74 46: 2 0 4
75 47: 2 4 1
76 47: 3 2 2
77 47: 4 0 3
78 48: 0 3 3
79 48: 1 1 4
80 48: 4 4 0
81 49: 2 3 2
82 49: 3 1 3
83 50: 0 2 4
84 50: 4 3 1
85 51: 1 4 2
86 51: 2 2 3
87 51: 3 0 4
88 52: 3 4 1
89 52: 4 2 2
90 53: 1 3 3
91 53: 2 1 4
92 54: 3 3 2
93 54: 4 1 3
94 55: 0 4 3
95 55: 1 2 4
96 56: 2 4 2
97 56: 3 2 3
98 56: 4 0 4
99 57: 0 3 4
100 57: 4 4 1
101 58: 2 3 3
102 58: 3 1 4
103 59: 4 3 2
104 60: 1 4 3
105 60: 2 2 4
106 61: 3 4 2
107 61: 4 2 3
108 62: 1 3 4
109 63: 3 3 3
110 63: 4 1 4
111 64: 0 4 4
112 65: 2 4 3
113 65: 3 2 4
114 66: 4 4 2
115 67: 2 3 4
116 68: 4 3 3
117 69: 1 4 4
118 70: 3 4 3
119 70: 4 2 4
120 72: 3 3 4
121 74: 2 4 4
122 75: 4 4 3
123 77: 4 3 4
124 79: 3 4 4
125 84: 4 4 4
这是一个简单的编译时整数排序。它为每个元素工作,计算出它在列表中的位置。从这里它计算出每个位置应该是什么。然后它构建一个插入到适当位置的新列表。就复杂性(它是 O(n^2))而言,它可能不如以前的解决方案有效,但它更容易理解并且不使用递归。
#include <initializer_list>
#include <array>
#include <tuple>
template<int... members>
struct IntList
{
constexpr bool operator==(IntList) const { return true; }
template<int... others>
constexpr bool operator==(IntList<others...>) const { return false; }
template<int idx>
static constexpr auto at()
{
return std::get<idx>(std::make_tuple(members...));
}
template<int x>
static constexpr auto indexOf()
{
int sum {};
auto _ = { 0, (x > members ? ++sum : 0)... };
return sum;
}
template<int x>
static constexpr auto count()
{
int sum {};
auto _ = { 0, (x == members ? ++sum : 0)... };
return sum;
}
template<int i>
static constexpr auto ith()
{
int result{};
auto _ = {
( i >= indexOf<members>() && i < indexOf<members>() + count<members>() ?
result = members : 0 )...
};
return result;
}
template<std::size_t... i>
static constexpr auto sortImpl(std::index_sequence<i...>)
{
return IntList< ith<i>()... >();
}
static constexpr auto sort()
{
return sortImpl(std::make_index_sequence<sizeof...(members)>());
}
};
static_assert(IntList<1, 2, 3>().at<1>() == 2, "");
static_assert(IntList<>().indexOf<1>() == 0, "");
static_assert(IntList<1>().indexOf<1>() == 0, "");
static_assert(IntList<1, 2, 3, 4>().indexOf<3>() == 2, "");
static_assert(IntList<>().count<1>() == 0, "");
static_assert(IntList<1>().count<1>() == 1, "");
static_assert(IntList<1, 1>().count<1>() == 2, "");
static_assert(IntList<2, 2, 1>().count<1>() == 1, "");
static_assert(IntList<1, 2, 1>().count<1>() == 2, "");
static_assert(IntList<>().sort() == IntList<>(), "");
static_assert(IntList<1>().sort() == IntList<1>(), "");
static_assert(IntList<1, 2>().sort() == IntList<1, 2>(), "");
static_assert(IntList<2, 1>().sort() == IntList<1, 2>(), "");
static_assert(IntList<3, 2, 1>().sort() == IntList<1, 2, 3>(), "");
static_assert(IntList<2, 2, 1>().sort() == IntList<1, 2, 2>(), "");
static_assert(IntList<4, 7, 2, 5, 1>().sort() == IntList<1, 2, 4, 5, 7>(), "");
static_assert(IntList<4, 7, 7, 5, 1, 1>().sort() == IntList<1, 1, 4, 5, 7, 7>(), "");
您可以实现 skew heap 在编译时对整数进行排序。以下示例适用于 c++17。
#include <type_traits>
#include <utility>
template <class T, T... s>
using iseq = std::integer_sequence<T, s...>;
template <class T, T v>
using ic = std::integral_constant<T, v>;
template <class T, T v1, T v2>
constexpr auto ic_less_impl(ic<T, v1>, ic<T, v2>) -> ic<bool, v1 < v2>;
template <class ic1, class ic2>
using ic_less = decltype(ic_less_impl(ic1(), ic2()));
template <bool b>
using bool_cond_t = std::conditional_t<b, std::true_type, std::false_type>;
struct nil {};
template <class T, T v, T... s>
constexpr auto iseq_front_impl(iseq<T, v, s...>) -> ic<T, v>;
template <class T>
constexpr auto iseq_front_impl(iseq<T>) -> nil;
template <class seq>
using iseq_front = decltype(iseq_front_impl(seq()));
template <class T, T v, T... s>
constexpr auto iseq_pop_front_impl(iseq<T, v, s...>) -> iseq<T, s...>;
template <class seq>
using iseq_pop_front = decltype(iseq_pop_front_impl(seq()));
template <class T, T v, T... s>
constexpr auto iseq_append_impl(iseq<T, s...>, ic<T, v>) -> iseq<T, s..., v>;
template <class T, T v>
constexpr auto iseq_append_impl(nil, ic<T, v>) -> iseq<T, v>;
template <class seq, class c>
using iseq_append = decltype(iseq_append_impl(seq(), c()));
template <class seq>
using iseq_is_empty = bool_cond_t<std::is_same<iseq_front<seq>, nil>::value>;
template <class X, class L, class R>
struct skew_heap {};
template <class X, class L, class R>
constexpr auto skh_get_top_impl(skew_heap<X, L, R>) -> X;
template <class H>
using skh_get_top = decltype(skh_get_top_impl(H()));
template <class X, class L, class R>
constexpr auto skh_get_left_impl(skew_heap<X, L, R>) -> L;
template <class H>
using skh_get_left = decltype(skh_get_left_impl(H()));
template <class X, class L, class R>
constexpr auto skh_get_right_impl(skew_heap<X, L, R>) -> R;
template <class H>
using skh_get_right = decltype(skh_get_right_impl(H()));
template <class H>
using skh_is_empty = bool_cond_t<std::is_same<H, nil>::value>;
template <class H1, class H2>
constexpr auto skh_merge_impl(H1, H2) -> decltype(auto) {
if constexpr (skh_is_empty<H1>::value) {
return H2{};
} else if constexpr (skh_is_empty<H2>::value) {
return H1{};
} else {
using x1 = skh_get_top<H1>;
using l1 = skh_get_left<H1>;
using r1 = skh_get_right<H1>;
using x2 = skh_get_top<H2>;
using l2 = skh_get_left<H2>;
using r2 = skh_get_right<H2>;
if constexpr (ic_less<x2, x1>::value) {
using new_r2 = decltype(skh_merge_impl(H1(), r2()));
return skew_heap<x2, new_r2, l2> {};
} else {
using new_r1 = decltype(skh_merge_impl(r1(), H2()));
return skew_heap<x1, new_r1, l1>{};
}
}
}
template <class H1, class H2>
using skh_merge = decltype(skh_merge_impl(H1(), H2()));
template <class H1, class IC1>
using skh_push = skh_merge<H1, skew_heap<IC1, nil, nil>>;
template <class H>
using skh_pop = skh_merge<skh_get_left<H>, skh_get_right<H>>;
template <class H, class seq>
constexpr auto skh_heapify_impl(H, seq) -> decltype(auto) {
if constexpr (iseq_is_empty<seq>::value) {
return H{};
} else {
using val = iseq_front<seq>;
return skh_heapify_impl(skh_push<H, val>{}, iseq_pop_front<seq>{});
}
}
template <class seq>
using skh_heapify = decltype(skh_heapify_impl(nil(), seq()));
template <class H, class seq>
constexpr auto skh_to_sortseq_impl(H, seq) -> decltype(auto) {
if constexpr (skh_is_empty<H>::value) {
return seq{};
} else {
using val = skh_get_top<H>;
return skh_to_sortseq_impl(skh_pop<H>{}, iseq_append<seq, val>{});
}
}
template <class H>
using skh_to_sortseq = decltype(skh_to_sortseq_impl(H(), nil()));
template <class seq>
using sort_seq = skh_to_sortseq<skh_heapify<seq>>;
static_assert(std::is_same<iseq<int, 1, 2, 3, 4, 5, 6, 7, 8, 9>, sort_seq<iseq<int, 2, 3, 5, 8, 9, 6, 7, 1, 4>>>::value);
我有一个 class A
有一个 std::vector<int>
作为属性。
A
需要在创建 A
的实例时填充此向量。
计算可能需要一些时间,我想知道是否:
- 可以在编译时完成。
- 向量也可以在编译时排序
我对元编程不熟悉,暂时没有找到方法。这不是 OS 特定的问题。
这是 A.cpp
文件:
#include "A.h"
#define SIZEV 100
A::A()
{
fillVector();
}
void A::fillVector()
{
// m_vector is an attribute of class "A"
// EXPECTATION 1 : fill the vector with the following calculation at compile time
const int a=5;
const int b=7;
const int c=9;
for(int i=0;i<SIZEV;i++){
for(int j=0;j<SIZEV;j++){
for(int k=0;k<SIZEV;k++){
this->m_vector.push_back(a*i+b*j+c*k);
}
}
}
// EXPECTATION 2 : sort the vector as compile time
}
std::vector<int> A::getVector() const
{
return m_vector;
}
void A::setVector(const std::vector<int> &vector)
{
m_vector = vector;
}
和main.cpp
(Qt应用程序但没关系):
#include <QCoreApplication>
#include "A.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
A a;
auto vec = a.getVector();
// use vec, etc ...
return app.exec();
}
A std::vector<int>
没有任何 constexpr
构造函数(因为 constexpr
不允许动态内存分配)。所以你不能在编译时对 std::vector<int>
进行排序。
您可以在编译时为常量 N
创建 std::array<int, N>
,但您必须编写自己的排序例程,因为 std::sort
不是 constexpr
任何一个。
您还可以编写 Boost.MPL 编译时向量或列表并使用其 sort
例程。但这不会像 std::array
.
另一个攻击角度可能是将向量存储到 static
变量中并在程序初始化时进行排序。您的程序只是需要更长的时间来启动,但不会影响其其余主要功能。
由于排序是 O(N log N)
,您甚至可以进行两步构建并将排序后的向量写入文件,然后 compile/link 将其写入主程序,或将其加载到 O(N)
在程序启动时进入 static
变量。
可预先计算的冗长计算的经典方法是将计算结果作为构建过程的一部分,生成 .cpp
对结果进行硬编码(在具有嵌入式资源的平台上,可以使用这些以及)。 .
不过,这里的计算非常简单,慢的部分大概就是分配,如果你想把数据保持在一个std::vector
,有 在运行时发生。如果您可以使用 C 风格的数组,您可以如上所述将它全部放入可执行文件中,但这会产生一个大 4 MB 的可执行文件,并且从磁盘加载它所导致的减速将抵消预先计算的任何速度优势。
IOW:当计算量大且输出量小时,在构建时进行预计算是有意义的。你的情况与频谱完全相反,所以我会避免它。
也许不完全是您要查找的内容,但您可以编写一个单独的程序来计算向量,对其进行排序,然后将其输出到列表中。然后你就可以读入那个文件了。
如果从磁盘读取太慢,您还可以将输出整理成一个合法的 C++ 文件,该文件初始化您的 class 的实例,该实例具有满足您要求的硬编码值。然后可以将其链接回您的主项目并进行编译,本质上提供与您在此处概述的更复杂的元编程任务相同的功能。
虽然可以这样做 (live example),但您不应该这样做。这将占用大量编译时间。
编译器不是为快速、高效的数字批量处理而设计的。现在,将您的编译时工作限制在相对简单的事情上,而不是对 1000 万个元素进行排序。
即使您编写 "compliant" 代码,今天的大多数编译器也会对您产生影响。我写的代码很早就死了,尽管我试图小心我的递归深度限制。
总之,为子孙后代:
template<class T>struct tag{using type=T;};
template<class Tag>using type_t=typename Tag::type;
template<int...Xs> struct values { constexpr values() {}; };
template<int...Xs> constexpr values<Xs...> values_v = {};
template<class...Vs> struct append;
template<class...Vs> using append_t=type_t<append<Vs...>>;
template<class...Vs> constexpr append_t<Vs...> append_v = {};
template<> struct append<>:tag<values<>>{};
template<int...Xs>struct append<values<Xs...>>:tag<values<Xs...>>{};
template<int...Lhs, int...Rhs, class...Vs>
struct append<values<Lhs...>,values<Rhs...>,Vs...>:
tag<append_t<values<Lhs...,Rhs...>,Vs...>>
{};
template<int...Lhs>
constexpr values<Lhs...> simple_merge( values<Lhs...>, values<> ) { return {}; }
template<int...Rhs>
constexpr values<Rhs...> simple_merge( values<>, values<Rhs...> ) { return {}; }
constexpr values<> simple_merge( values<>, values<> ) { return {}; }
template<int L0, int...Lhs, int R0, int...Rhs>
constexpr auto simple_merge( values<L0, Lhs...>, values<R0, Rhs...> )
-> std::conditional_t<
(R0 < L0),
append_t< values<R0>, decltype( simple_merge( values<L0,Lhs...>{}, values<Rhs...>{} ) ) >,
append_t< values<L0>, decltype( simple_merge( values<Lhs...>{}, values<R0, Rhs...>{} ) ) >
> {
return {};
}
template<class Lhs, class Rhs>
using simple_merge_t = decltype( simple_merge( Lhs{}, Rhs{} ) );
template<class Lhs, class Rhs>
constexpr simple_merge_t<Lhs, Rhs> simple_merge_v = {};
template<class Values, size_t I> struct split
{
private:
using one = split<Values, I/2>;
using two = split<typename one::rhs, I-I/2>;
public:
using lhs = append_t< typename one::lhs, typename two::lhs >;
using rhs = typename two::rhs;
};
template<class Values, size_t I> using split_t=type_t<split<Values, I>>;
template<class Values> struct split<Values, 0>{
using lhs = values<>;
using rhs = Values;
};
template<int X0, int...Xs> struct split<values<X0, Xs...>, 1> {
using lhs = values<X0>;
using rhs = values<Xs...>;
};
template<class Values, size_t I> using before_t = typename split<Values, I>::lhs;
template<class Values, size_t I> using after_t = typename split<Values, I>::rhs;
template<size_t I>using index_t=std::integral_constant<size_t, I>;
template<int I>using int_t=std::integral_constant<int, I>;
template<int I>constexpr int_t<I> int_v={};
template<class Values> struct front;
template<int X0, int...Xs> struct front<values<X0, Xs...>>:tag<int_t<X0>>{};
template<class Values> using front_t=type_t<front<Values>>;
template<class Values> constexpr front_t<Values> front_v = {};
template<class Values, size_t I>
struct get:tag<front_t< after_t<Values, I> >> {};
template<class Values, size_t I> using get_t = type_t<get<Values, I>>;
template<class Values, size_t I> constexpr get_t<Values, I> get_v = {};
template<class Values>
struct length;
template<int...Xs>
struct length<values<Xs...>>:tag<index_t<sizeof...(Xs)>> {};
template<class Values> using length_t = type_t<length<Values>>;
template<class Values> constexpr length_t<Values> length_v = {};
template<class Values> using front_half_t = before_t< Values, length_v<Values>/2 >;
template<class Values> constexpr front_half_t<Values> front_half_v = {};
template<class Values> using back_half_t = after_t< Values, length_v<Values>/2 >;
template<class Values> constexpr back_half_t<Values> back_half_v = {};
template<class Lhs, class Rhs>
struct least : tag< std::conditional_t< (Lhs{}<Rhs{}), Lhs, Rhs > > {};
template<class Lhs, class Rhs> using least_t = type_t<least<Lhs, Rhs>>;
template<class Lhs, class Rhs>
struct most : tag< std::conditional_t< (Lhs{}>Rhs{}), Lhs, Rhs > > {};
template<class Lhs, class Rhs> using most_t = type_t<most<Lhs, Rhs>>;
template<class Values>
struct pivot {
private:
using a = get_t<Values, 0>;
using b = get_t<Values, length_v<Values>/2>;
using c = get_t<Values, length_v<Values>-1>;
using d = most_t< least_t<a,b>, most_t< least_t<b,c>, least_t<a,c> > >;
public:
using type = d;
};
template<int X0, int X1>
struct pivot<values<X0, X1>>: tag< most_t< int_t<X0>, int_t<X1> > > {};
template<class Values> using pivot_t = type_t<pivot<Values>>;
template<class Values> constexpr pivot_t<Values> pivot_v = {};
template<int P>
constexpr values<> lower_split( int_t<P>, values<> ) { return {}; }
template<int P, int X0>
constexpr std::conditional_t< (X0<P), values<X0>, values<> > lower_split( int_t<P>, values<X0> ) { return {}; }
template<int P, int X0, int X1, int...Xs >
constexpr auto lower_split( int_t<P>, values<X0, X1, Xs...> )
-> append_t<
decltype(lower_split( int_v<P>, front_half_v<values<X0, X1, Xs...>> )),
decltype(lower_split( int_v<P>, back_half_v<values<X0, X1, Xs...>> ))
>{ return {}; }
template<int P>
constexpr values<> upper_split( int_t<P>, values<> ) { return {}; }
template<int P, int X0>
constexpr std::conditional_t< (X0>P), values<X0>, values<> > upper_split( int_t<P>, values<X0> ) { return {}; }
template<int P, int X0, int X1, int...Xs>
constexpr auto upper_split( int_t<P>, values<X0, X1, Xs...> )
-> append_t<
decltype(upper_split( int_v<P>, front_half_v<values<X0, X1, Xs...>> )),
decltype(upper_split( int_v<P>, back_half_v<values<X0, X1, Xs...>> ))
>{ return {}; }
template<int P>
constexpr values<> middle_split( int_t<P>, values<> ) { return {}; }
template<int P, int X0>
constexpr std::conditional_t< (X0==P), values<X0>, values<> > middle_split( int_t<P>, values<X0> ) { return {}; }
template<int P, int X0, int X1, int...Xs>
constexpr auto middle_split( int_t<P>, values<X0, X1, Xs...> )
-> append_t<
decltype(middle_split( int_v<P>, front_half_v<values<X0, X1, Xs...>> )),
decltype(middle_split( int_v<P>, back_half_v<values<X0, X1, Xs...>> ))
>{ return {}; }
template<class Values>
using lower_split_t = decltype(lower_split( pivot_v<Values>, Values{} ) );
template<class Values> constexpr lower_split_t<Values> lower_split_v = {};
template<class Values>
using upper_split_t = decltype(upper_split( pivot_v<Values>, Values{} ) );
template<class Values> constexpr upper_split_t<Values> upper_split_v = {};
template<class Values>
using middle_split_t = decltype(middle_split( pivot_v<Values>, Values{} ) );
template<class Values> constexpr middle_split_t<Values> middle_split_v = {};
constexpr values<> simple_merge_sort( values<> ) { return {}; }
template<int X>
constexpr values<X> simple_merge_sort( values<X> ) { return {}; }
template<class Values>
using simple_merge_sort_t = decltype( simple_merge_sort( Values{} ) );
template<class Values>
constexpr simple_merge_sort_t<Values> simple_merge_sort_v = {};
template<int X0, int X1, int...Xs>
constexpr auto simple_merge_sort( values<X0, X1, Xs...> )
->
simple_merge_t<
simple_merge_t<
simple_merge_sort_t<lower_split_t<values<X0, X1, Xs...>>>, simple_merge_sort_t<upper_split_t<values<X0, X1, Xs...>>>
>,
middle_split_t<values<X0, X1, Xs...>>
>
{ return {}; }
template<class Values>constexpr Values cross_add( Values ) { return {}; }
template<class Values>constexpr values<> cross_add( values<>, Values ) { return {}; }
template<int A0, int...B>constexpr values<(B+A0)...> cross_add( values<A0>, values<B...> ) { return {}; }
template<int A0, int A1, int...A, int...B>
constexpr auto cross_add( values<A0, A1, A...>, values<B...>)
-> append_t<
decltype(cross_add( front_half_v<values<A0, A1, A...>>, values_v<B...> ) ),
decltype(cross_add( back_half_v<values<A0, A1, A...>>, values_v<B...> ) )
> { return {}; }
template<class V0, class V1, class V2, class... Vs>
constexpr auto cross_add( V0, V1, V2, Vs... )
-> decltype(
cross_add( cross_add( V0{}, V1{} ), V2{}, Vs{}... )
) { return {}; }
template<class...Vs>
using cross_add_t = decltype( cross_add(Vs{}...) );
template<class...Vs>
constexpr cross_add_t<Vs...> cross_add_v = {};
template<int X, int...Xs>
constexpr values<(X*Xs)...> scale( int_t<X>, values<Xs...> ) { return {}; }
template<class X, class Xs>
using scale_t = decltype( scale(X{}, Xs{}) );
template<class X, class Xs> constexpr scale_t<X,Xs> scale_v = {};
template<int X0, int...Xs> struct generate_values : generate_values<X0-1, X0-1, Xs...> {};
template<int...Xs> struct generate_values<0,Xs...>:tag<values<Xs...>>{};
template<int X0> using generate_values_t = type_t<generate_values<X0>>;
三中值编译时合并排序和叉积生成器。通过努力,我可以大大减少行数。
使用 constexpr std::array
可能会比上述纯类型解决方案更快。
数据是0
到SIZEV * (a+b+c)
的整数,但是整数的个数是SIZEV
3。它是一组密集的整数,范围很小,所以 CountingSort 是完美的(你永远不需要构建未排序的数组,只需要在生成时递增计数)。
不管保持计数/前缀总和,CountingSort 绝对会在启动时间上取得巨大胜利,对向量进行排序,与其他排序相比,保持其他一切相同。
您可以将数据的紧凑形式(O(cuberoot(n)) 大小)保留为 prefix sums 的向量,以便从 m_vector 中查找 O(log (cuberoot(n) ))) 时间(二进制搜索前缀和),其中 n 是 m_vector 的长度。见下文。
取决于缓存/内存延迟,不实际扩展 m_vector 可能会也可能不会是性能上的胜利。如果需要一系列值,您可以从前缀和中快速生成 m_vector 的连续元素。
class A {
// vector<uint16_t> m_counts; // needs to be 32b for SIZEV>=794 (found experimentally).
vector<uint32_t> m_pos; // values are huge: indices into m_vector, up to SIZEV**3 - 1
vector<uint16_t> m_vector; // can be 16b until SIZEV>3121: max val is only (a+b+c) * (SIZEV-1)
}
void A::fillVector()
{
const int a=5;
const int b=7;
const int c=9;
const auto max_val = (SIZEV-1) * (a+b+c);
m_vector.reserve(SIZEV*SIZEV*SIZEV);
m_vector.resize(0);
// or clear it, but that writes tons of mem, unless you use a custom Allocator::construct to leave it uninit
// http://en.cppreference.com/w/cpp/container/vector/resize
m_pos.resize(max_val + 1); // again, ideally avoid zeroing
// but if not, do it before m_counts
m_counts.clear(); // do this one last, so it's hot in cache even if others wasted time writing zeros.
m_counts.resize(max_val + 1); // vector is now zeroed
// Optimization: don't have a separate m_counts.
// zero and count into m_pos, then do prefix summing in-place
// manually strength-reduce the multiplication to addition
// in case the compiler decides it won't, or can't prove it won't overflow the same way
// Not necessary with gcc or clang: they both do this already
for(int kc=c*(SIZEV-1) ; kc >= 0 ; kc-=c) {
for(int jb=b*(SIZEV-1) ; jb >= 0 ; jb-=b) {
for(int ia=a*(SIZEV-1) ; ia >= 0 ; ia-=a) {
m_counts[kc + jb + ia]++;
// do the smallest stride in the inner-most loop, for better cache locality
}
}
}
// write the early elements last, so they'll be hot in the cache when we're done
int val = 0;
uint32_t sum = 0;
for ( auto &count : m_counts ) {
m_vector.insert(m_vector.end(), count, val++);
// count is allowed to be zero for vector::insert(pos, count, value)
m_pos[val] = sum; // build our vector of prefix sums
sum += count;
//count = (sum+=count); // in-place conversion to prefix sums
}
assert(m_vector.size() == SIZEV*SIZEV*SIZEV);
}
或者,不是实际扩展 1.6GB 数组,而是计算 Prefix sums,为您提供该索引的 运行 起始位置的向量作为 m_vector
。即 idx = m_pos[val]; m_vector[idx] == val
。 (这分解为 val <= 13,其中有些值不能表示为 a、b 和 c 的总和,因此 m_count
中有零,并在 m_pos
中重复)
无论如何,您可以用 m_pos
中的 i
的二进制搜索替换对 m_vector[i]
的读取。您正在寻找 m_pos
中值 <= i 的最高索引。该索引就是您在 m_vector[i]
中找到的内容。 (或类似的东西;我可能有一个差一的错误。)
散列 table 将不起作用,因为您需要将 i
的多个值映射到 0..(750*(a+b+c)) 中的每个数字。 (m_vector[i]
具有相同值的所有 i
。)
如果您需要 运行 个顺序元素,请将它们即时生成到 tmp 缓冲区中。查看 m_pos[i+1]
以查看下一个具有不同值的元素何时到来。 (查看 m_counts
可能会节省一些减法,但您最好只取 m_pos
中的差异来反转前缀和,以避免缓存未命中/缓存污染接触第二个数组。)
实际上,m_counts
可能根本不需要作为 class 成员保留,只是 FillVector 中的临时成员。或者 FillVector 可以算入 m_pos
,并将其就地转换为前缀和。
理想情况下,您可以使用模板做一些聪明的事情,为 m_counts 和 m_vector 选择足够宽但不超过需要的类型。 IDK数论,所以我不知道如何证明不会有一桶m_counts
溢出一个uint16_t
。 平均 计数将为 750**3 / (750*(5+7+9)) = 26786,并且它们肯定聚集在 m_counts
的高端.在实践中,SIZEV=793 可以使用 uint16_t 计数器,而 SIZEV=794 produces several counts > 65536 (感谢 Chris 提供的工作示例,我可以在其中轻松测试它)。
m_vector
可以是 uint16_t
直到 (SIZEV-1)*(a+b+c) > MAX_UINT16
(65535)。即直到 SIZEV >= 3122,此时 m_vector
占用 28.3 GiB 的 RAM。
在 SIZEV = 750 时,m_pos
大约是 L1 缓存大小的 2 倍(Intel CPU)(750*(5+7+9) * 4B per short = 63000B
)。如果编译器做得很好并使用条件移动而不是 unpredictable 分支指令进行二进制搜索,这可能会非常快。它肯定会为您节省大量的主内存流量,如果您有多个线程,这很有价值。
或者,永远不要触及 m_vector
意味着您可以 处理问题大小,这需要比您 存储列表更多的内存。
如果您想在首先创建 m_counts 时(使用三重嵌套循环)通过缓存优化获得真正的创意,请让最内层的循环向前然后向后,而不是相同方向两次。这只会影响非常大的 SIZEV,或者如果另一个超线程对缓存施加很大压力。
for(int kc=c*(SIZEV-1) ; kc >= 0 ; kc-=c) {
for(int jb=b*(SIZEV-1) ; jb >= 0 ; jb-=b) {
for(int ia=0 ; ia<SIZEV*a ; ia+=a)
counts[kc + jb + ia]++;
if (! (jb-=b )) break;
for(int ia=a*(SIZEV-1) ; ia >= 0 ; ia-=a)
counts[kc + jb + ia]++;
}
}
向零倒计时(有或没有双向内部循环)很可能是下一个循环开始的小胜利,在计数变高时执行大 memsets 成为内存限制之前。扫描前向以就地进行前缀和也是一个胜利。
我之前的回答,可能是死路一条:
是否有希望找到排序向量中第 i
个元素的闭式公式?或者甚至是用于动态生成它的 O(log i) 算法?
除非您在访问该向量时需要大量顺序元素,否则可能动态计算它会更快。内存很慢,CPU 很快,所以如果你能在大约 150 个时钟周期内计算出 a[i]
,你就领先了。 (假设每次访问都是缓存未命中,或者不接触所有向量内存会减少程序其余部分的缓存未命中)。
如果我们能做到这一点,理论上我们可以首先按顺序编写排序数组。
要做到这一点:打乱常量 a <= b <= c
。
0, a, [a*2 .. a*int(b/a)], b, [b + a .. b + a*int((c-b)/a) mixed with b*2 .. b*int(c/b)], c, [some number of b*x + a*y], c+a, [more b*x + a*y], ...
好的,所以这变成了组合混乱,这个想法可能不可行。至少,不是对于任何 a、b 和 c 的一般情况。
a=5,b=7,c=9:
0, 5=a, 7=b, 9=c, 10=2a, 12=b+a, 14=2b, 14=c+a, 15=3a, 16=c+b, 18 =2c
我太困了,看不到模式,但这里有一个更长的列表
# bash
limit=5; for ((i=0 ; i<limit ; i++)); do
for ((j=0 ; j<limit ; j++)); do
for ((k=0 ; k<limit ; k++)); do
printf "%2d: %d %d %d\n" $((5*i + 7*j + 9*k)) $i $j $k;
done; done; done | sort -n | cat -n
1 0: 0 0 0
2 5: 1 0 0
3 7: 0 1 0
4 9: 0 0 1
5 10: 2 0 0
6 12: 1 1 0
7 14: 0 2 0
8 14: 1 0 1
9 15: 3 0 0
10 16: 0 1 1
11 17: 2 1 0
12 18: 0 0 2
13 19: 1 2 0
14 19: 2 0 1
15 20: 4 0 0
16 21: 0 3 0
17 21: 1 1 1
18 22: 3 1 0
19 23: 0 2 1
20 23: 1 0 2
21 24: 2 2 0
22 24: 3 0 1
23 25: 0 1 2
24 26: 1 3 0
25 26: 2 1 1
26 27: 0 0 3
27 27: 4 1 0
28 28: 0 4 0
29 28: 1 2 1
30 28: 2 0 2
31 29: 3 2 0
32 29: 4 0 1
33 30: 0 3 1
34 30: 1 1 2
35 31: 2 3 0
36 31: 3 1 1
37 32: 0 2 2
38 32: 1 0 3
39 33: 1 4 0
40 33: 2 2 1
41 33: 3 0 2
42 34: 0 1 3
43 34: 4 2 0
44 35: 1 3 1
45 35: 2 1 2
46 36: 0 0 4
47 36: 3 3 0
48 36: 4 1 1
49 37: 0 4 1
50 37: 1 2 2
51 37: 2 0 3
52 38: 2 4 0
53 38: 3 2 1
54 38: 4 0 2
55 39: 0 3 2
56 39: 1 1 3
57 40: 2 3 1
58 40: 3 1 2
59 41: 0 2 3
60 41: 1 0 4
61 41: 4 3 0
62 42: 1 4 1
63 42: 2 2 2
64 42: 3 0 3
65 43: 0 1 4
66 43: 3 4 0
67 43: 4 2 1
68 44: 1 3 2
69 44: 2 1 3
70 45: 3 3 1
71 45: 4 1 2
72 46: 0 4 2
73 46: 1 2 3
74 46: 2 0 4
75 47: 2 4 1
76 47: 3 2 2
77 47: 4 0 3
78 48: 0 3 3
79 48: 1 1 4
80 48: 4 4 0
81 49: 2 3 2
82 49: 3 1 3
83 50: 0 2 4
84 50: 4 3 1
85 51: 1 4 2
86 51: 2 2 3
87 51: 3 0 4
88 52: 3 4 1
89 52: 4 2 2
90 53: 1 3 3
91 53: 2 1 4
92 54: 3 3 2
93 54: 4 1 3
94 55: 0 4 3
95 55: 1 2 4
96 56: 2 4 2
97 56: 3 2 3
98 56: 4 0 4
99 57: 0 3 4
100 57: 4 4 1
101 58: 2 3 3
102 58: 3 1 4
103 59: 4 3 2
104 60: 1 4 3
105 60: 2 2 4
106 61: 3 4 2
107 61: 4 2 3
108 62: 1 3 4
109 63: 3 3 3
110 63: 4 1 4
111 64: 0 4 4
112 65: 2 4 3
113 65: 3 2 4
114 66: 4 4 2
115 67: 2 3 4
116 68: 4 3 3
117 69: 1 4 4
118 70: 3 4 3
119 70: 4 2 4
120 72: 3 3 4
121 74: 2 4 4
122 75: 4 4 3
123 77: 4 3 4
124 79: 3 4 4
125 84: 4 4 4
这是一个简单的编译时整数排序。它为每个元素工作,计算出它在列表中的位置。从这里它计算出每个位置应该是什么。然后它构建一个插入到适当位置的新列表。就复杂性(它是 O(n^2))而言,它可能不如以前的解决方案有效,但它更容易理解并且不使用递归。
#include <initializer_list>
#include <array>
#include <tuple>
template<int... members>
struct IntList
{
constexpr bool operator==(IntList) const { return true; }
template<int... others>
constexpr bool operator==(IntList<others...>) const { return false; }
template<int idx>
static constexpr auto at()
{
return std::get<idx>(std::make_tuple(members...));
}
template<int x>
static constexpr auto indexOf()
{
int sum {};
auto _ = { 0, (x > members ? ++sum : 0)... };
return sum;
}
template<int x>
static constexpr auto count()
{
int sum {};
auto _ = { 0, (x == members ? ++sum : 0)... };
return sum;
}
template<int i>
static constexpr auto ith()
{
int result{};
auto _ = {
( i >= indexOf<members>() && i < indexOf<members>() + count<members>() ?
result = members : 0 )...
};
return result;
}
template<std::size_t... i>
static constexpr auto sortImpl(std::index_sequence<i...>)
{
return IntList< ith<i>()... >();
}
static constexpr auto sort()
{
return sortImpl(std::make_index_sequence<sizeof...(members)>());
}
};
static_assert(IntList<1, 2, 3>().at<1>() == 2, "");
static_assert(IntList<>().indexOf<1>() == 0, "");
static_assert(IntList<1>().indexOf<1>() == 0, "");
static_assert(IntList<1, 2, 3, 4>().indexOf<3>() == 2, "");
static_assert(IntList<>().count<1>() == 0, "");
static_assert(IntList<1>().count<1>() == 1, "");
static_assert(IntList<1, 1>().count<1>() == 2, "");
static_assert(IntList<2, 2, 1>().count<1>() == 1, "");
static_assert(IntList<1, 2, 1>().count<1>() == 2, "");
static_assert(IntList<>().sort() == IntList<>(), "");
static_assert(IntList<1>().sort() == IntList<1>(), "");
static_assert(IntList<1, 2>().sort() == IntList<1, 2>(), "");
static_assert(IntList<2, 1>().sort() == IntList<1, 2>(), "");
static_assert(IntList<3, 2, 1>().sort() == IntList<1, 2, 3>(), "");
static_assert(IntList<2, 2, 1>().sort() == IntList<1, 2, 2>(), "");
static_assert(IntList<4, 7, 2, 5, 1>().sort() == IntList<1, 2, 4, 5, 7>(), "");
static_assert(IntList<4, 7, 7, 5, 1, 1>().sort() == IntList<1, 1, 4, 5, 7, 7>(), "");
您可以实现 skew heap 在编译时对整数进行排序。以下示例适用于 c++17。
#include <type_traits>
#include <utility>
template <class T, T... s>
using iseq = std::integer_sequence<T, s...>;
template <class T, T v>
using ic = std::integral_constant<T, v>;
template <class T, T v1, T v2>
constexpr auto ic_less_impl(ic<T, v1>, ic<T, v2>) -> ic<bool, v1 < v2>;
template <class ic1, class ic2>
using ic_less = decltype(ic_less_impl(ic1(), ic2()));
template <bool b>
using bool_cond_t = std::conditional_t<b, std::true_type, std::false_type>;
struct nil {};
template <class T, T v, T... s>
constexpr auto iseq_front_impl(iseq<T, v, s...>) -> ic<T, v>;
template <class T>
constexpr auto iseq_front_impl(iseq<T>) -> nil;
template <class seq>
using iseq_front = decltype(iseq_front_impl(seq()));
template <class T, T v, T... s>
constexpr auto iseq_pop_front_impl(iseq<T, v, s...>) -> iseq<T, s...>;
template <class seq>
using iseq_pop_front = decltype(iseq_pop_front_impl(seq()));
template <class T, T v, T... s>
constexpr auto iseq_append_impl(iseq<T, s...>, ic<T, v>) -> iseq<T, s..., v>;
template <class T, T v>
constexpr auto iseq_append_impl(nil, ic<T, v>) -> iseq<T, v>;
template <class seq, class c>
using iseq_append = decltype(iseq_append_impl(seq(), c()));
template <class seq>
using iseq_is_empty = bool_cond_t<std::is_same<iseq_front<seq>, nil>::value>;
template <class X, class L, class R>
struct skew_heap {};
template <class X, class L, class R>
constexpr auto skh_get_top_impl(skew_heap<X, L, R>) -> X;
template <class H>
using skh_get_top = decltype(skh_get_top_impl(H()));
template <class X, class L, class R>
constexpr auto skh_get_left_impl(skew_heap<X, L, R>) -> L;
template <class H>
using skh_get_left = decltype(skh_get_left_impl(H()));
template <class X, class L, class R>
constexpr auto skh_get_right_impl(skew_heap<X, L, R>) -> R;
template <class H>
using skh_get_right = decltype(skh_get_right_impl(H()));
template <class H>
using skh_is_empty = bool_cond_t<std::is_same<H, nil>::value>;
template <class H1, class H2>
constexpr auto skh_merge_impl(H1, H2) -> decltype(auto) {
if constexpr (skh_is_empty<H1>::value) {
return H2{};
} else if constexpr (skh_is_empty<H2>::value) {
return H1{};
} else {
using x1 = skh_get_top<H1>;
using l1 = skh_get_left<H1>;
using r1 = skh_get_right<H1>;
using x2 = skh_get_top<H2>;
using l2 = skh_get_left<H2>;
using r2 = skh_get_right<H2>;
if constexpr (ic_less<x2, x1>::value) {
using new_r2 = decltype(skh_merge_impl(H1(), r2()));
return skew_heap<x2, new_r2, l2> {};
} else {
using new_r1 = decltype(skh_merge_impl(r1(), H2()));
return skew_heap<x1, new_r1, l1>{};
}
}
}
template <class H1, class H2>
using skh_merge = decltype(skh_merge_impl(H1(), H2()));
template <class H1, class IC1>
using skh_push = skh_merge<H1, skew_heap<IC1, nil, nil>>;
template <class H>
using skh_pop = skh_merge<skh_get_left<H>, skh_get_right<H>>;
template <class H, class seq>
constexpr auto skh_heapify_impl(H, seq) -> decltype(auto) {
if constexpr (iseq_is_empty<seq>::value) {
return H{};
} else {
using val = iseq_front<seq>;
return skh_heapify_impl(skh_push<H, val>{}, iseq_pop_front<seq>{});
}
}
template <class seq>
using skh_heapify = decltype(skh_heapify_impl(nil(), seq()));
template <class H, class seq>
constexpr auto skh_to_sortseq_impl(H, seq) -> decltype(auto) {
if constexpr (skh_is_empty<H>::value) {
return seq{};
} else {
using val = skh_get_top<H>;
return skh_to_sortseq_impl(skh_pop<H>{}, iseq_append<seq, val>{});
}
}
template <class H>
using skh_to_sortseq = decltype(skh_to_sortseq_impl(H(), nil()));
template <class seq>
using sort_seq = skh_to_sortseq<skh_heapify<seq>>;
static_assert(std::is_same<iseq<int, 1, 2, 3, 4, 5, 6, 7, 8, 9>, sort_seq<iseq<int, 2, 3, 5, 8, 9, 6, 7, 1, 4>>>::value);