<tt.rantala@gmail.com>
http://doi.acm.org/10.1145/1227161.1227164
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <iostream>
#include <boost/static_assert.hpp>
#include <boost/array.hpp>
#include <boost/type_traits/integral_constant.hpp>
#include "tools/debug.h"
#include "tools/get_char.h"
#include "tools/insertion_sort.h"
#include "../tools/contest.h"
namespace rantala {
struct Stream { unsigned char** restrict stream; size_t n; };
static void
check_input(unsigned char** from0, size_t n0)
{
(void) from0;
(void) n0;
#ifndef NDEBUG
for(size_t i=1;i<n0;++i)if(cmp(from0[i-1],from0[i])>0){
debug()<<"Oops: ''"<<from0[i-1]<<"'' > ''"<<from0[i]<<"''\n";}
for(size_t i=1;i<n0;++i)assert(cmp(from0[i-1],from0[i])<=0);
#endif
}
template <unsigned K>
static void
debug_print(const boost::array<size_t,K>& buffer_count,
const boost::array<Stream,K>& streams,
unsigned me, unsigned pos=1)
{
#ifndef NDEBUG
debug_indent;
if (pos >= K) {
std::string sample = "[";
for (size_t i=0; i < streams[pos-K].n; ++i) {
sample
+= std::string((char*)((streams[pos-K].stream)[i]))
+= ", ";
}
if (sample.size()>2) sample = sample.substr(0,sample.size()-2);
sample += "]";
debug()<<"("<<streams[pos-K].n<<") "<<sample<<"\n";
return;
}
debug_print(buffer_count, streams, me, 2*pos+1);
if (pos==1) {
if (pos==me) { debug()<<"(*)\n"; }
else { debug()<<"(o)\n"; }
} else {
if (pos==me) { debug()<<"* "<<buffer_count[pos]<<"\n"; }
else { debug()<<"- "<<buffer_count[pos]<<"\n"; }
}
debug_print(buffer_count, streams, me, 2*pos);
#endif
}
template <unsigned K, unsigned I> struct buffer_size;
template <unsigned K, unsigned I, bool> struct subtree_size_fwd;
template <unsigned K, unsigned I> struct subtree_size;
template <unsigned K, unsigned I> struct subtree_size_fwd<K,I,true>
{ enum { value = 2*buffer_size<K,I>::value
+ subtree_size<K,2*I>::value
+ subtree_size<K,2*I+1>::value }; };
template <unsigned K, unsigned I> struct subtree_size_fwd<K,I,false>
{ enum { value=0 }; };
template <unsigned K, unsigned I> struct subtree_size
{ enum { value=subtree_size_fwd<K,I,(I<K/2)>::value }; };
template <unsigned K, unsigned I> struct buffer_size
{ enum { value=buffer_size<K,I-1>::value }; };
template <unsigned K> struct buffer_size<K,0> { enum { value=0 }; };
template <unsigned K, unsigned I> struct buffer_layout_dfs
{ enum { lindex=(I%2==0?buffer_layout_dfs<K,I/2>::lindex
:buffer_layout_dfs<K,I/2>::rindex)
+ buffer_size<K,I/2>::value,
rindex=lindex+buffer_size<K,I>::value+subtree_size<K,2*I>::value }; };
template <unsigned K> struct buffer_layout_dfs<K,0>
{ enum { lindex=0, rindex=0 }; };
template <unsigned K, unsigned I> struct buffer_layout_bfs
{ enum { lindex=buffer_layout_bfs<K,I-1>::rindex+buffer_size<K,I-1>::value,
rindex=lindex+buffer_size<K,I>::value }; };
template <unsigned K> struct buffer_layout_bfs<K,0>
{ enum { lindex=0, rindex=0 }; };
#define MAKE_BUFFER_SIZE(K, I, SIZE) \
template <> struct buffer_size<K,I> { enum { value=SIZE }; };
MAKE_BUFFER_SIZE(8, 1, 8)
MAKE_BUFFER_SIZE(8, 2, 23)
MAKE_BUFFER_SIZE(16, 1, 8)
MAKE_BUFFER_SIZE(16, 2, 64)
MAKE_BUFFER_SIZE(16, 4, 8)
MAKE_BUFFER_SIZE(32, 1, 8)
MAKE_BUFFER_SIZE(32, 2, 23)
MAKE_BUFFER_SIZE(32, 4, 182)
MAKE_BUFFER_SIZE(32, 8, 8)
MAKE_BUFFER_SIZE(64, 1, 8)
MAKE_BUFFER_SIZE(64, 2, 23)
MAKE_BUFFER_SIZE(64, 4, 512)
MAKE_BUFFER_SIZE(64, 8, 8)
MAKE_BUFFER_SIZE(64, 16, 23)
MAKE_BUFFER_SIZE(128, 1, 8)
MAKE_BUFFER_SIZE(128, 2, 64)
MAKE_BUFFER_SIZE(128, 4, 32)
MAKE_BUFFER_SIZE(128, 8, 1449)
MAKE_BUFFER_SIZE(128, 16, 8)
MAKE_BUFFER_SIZE(128, 32, 23)
template <unsigned K, unsigned I=K/2-1>
struct buffer_total_size {
enum { value=buffer_total_size<K,I-1>::value+2*buffer_size<K,I>::value };
};
template <unsigned K> struct buffer_total_size<K,0> { enum { value = 0 }; };
BOOST_STATIC_ASSERT(buffer_total_size<8>::value==108);
BOOST_STATIC_ASSERT((buffer_layout_dfs<8,1>::lindex==0));
BOOST_STATIC_ASSERT((buffer_layout_dfs<8,2>::lindex==8));
BOOST_STATIC_ASSERT((buffer_layout_dfs<8,2>::rindex==8+23));
BOOST_STATIC_ASSERT((buffer_layout_dfs<8,1>::rindex==8+23+23));
BOOST_STATIC_ASSERT((buffer_layout_dfs<8,3>::lindex==8+23+23+8));
BOOST_STATIC_ASSERT((buffer_layout_dfs<8,3>::rindex==8+23+23+8+23));
BOOST_STATIC_ASSERT((subtree_size<8,1>::value==108));
BOOST_STATIC_ASSERT((subtree_size<8,2>::value==23+23));
BOOST_STATIC_ASSERT((subtree_size<8,3>::value==23+23));
BOOST_STATIC_ASSERT((subtree_size<8,4>::value==0));
BOOST_STATIC_ASSERT((subtree_size<8,5>::value==0));
BOOST_STATIC_ASSERT((subtree_size<8,6>::value==0));
BOOST_STATIC_ASSERT((subtree_size<8,7>::value==0));
template <unsigned K, unsigned I,
template <unsigned, unsigned> class BufferLayout> struct fill;
#define L2Ln streams[2*I-K ].n
#define L2Rn streams[2*I-K+1].n
#define L2Ls streams[2*I-K ].stream
#define L2Rs streams[2*I-K+1].stream
#define L2B (buffer+((I%2==0)?BufferLayout<K,I/2>::lindex:BufferLayout<K,I/2>::rindex))
#define L2Bsize (buffer_size<K,I/2>::value)
template <unsigned K,unsigned I,template <unsigned,unsigned> class BufferLayout>
static inline __attribute__((always_inline))
void fill_leaf(boost::array<Stream,K>&, unsigned char**,
boost::array<size_t,K>&, boost::false_type) {}
template <unsigned K,unsigned I,template <unsigned,unsigned> class BufferLayout>
static inline __attribute__((always_inline))
void fill_leaf(boost::array<Stream,K>& restrict streams,
unsigned char** restrict buffer,
boost::array<size_t,K>& restrict buffer_count,
boost::true_type)
{
debug() << __func__ << ", leaf, I="<<I<<"\n"; debug_indent;
assert(buffer_count[I] == 0);
if ((L2Ln + L2Rn) < L2Bsize) {
const size_t empty = (L2Bsize-L2Ln)-L2Rn;
size_t n = L2Bsize;
debug()<<"need="<<n<<", Ln="<<L2Ln<<", Rn="<<L2Rn<<"\n";
if (L2Ln==0) goto finish1_left;
if (L2Rn==0) goto finish1_right;
while (true) {
if (cmp(*(L2Ls), *(L2Rs)) <= 0) {
*(L2B+empty+(L2Bsize-n)) = *L2Ls;
--n;
++(L2Ls);
--(L2Ln);
if (L2Ln == 0) goto finish1_left;
} else {
*(L2B+empty+(L2Bsize-n)) = *L2Rs;
--n;
++(L2Rs);
--(L2Rn);
if (L2Rn == 0) goto finish1_right;
}
}
finish1_left:
debug()<<"left drained, Ln="<<L2Ln<<", Rn="<<L2Rn<<"\n";
std::copy(L2Rs, L2Rs+L2Rn, L2B+empty+L2Bsize-n);
L2Rn = 0;
buffer_count[I] = L2Bsize - empty;
return;
finish1_right:
debug()<<"right drained, Ln="<<L2Ln<<", Rn="<<L2Rn<<"\n";
std::copy(L2Ls, L2Ls+L2Ln, L2B+empty+L2Bsize-n);
L2Ln = 0;
buffer_count[I] = L2Bsize - empty;
check_input(L2B+empty, L2Bsize - empty);
return;
} else {
size_t n = L2Bsize;
debug()<<"need="<<n<<", Ln="<<L2Ln<<", Rn="<<L2Rn<<"\n";
if (L2Ln==0) goto finish2_left;
if (L2Rn==0) goto finish2_right;
while (n) {
if (cmp(*(L2Ls), *(L2Rs)) <= 0) {
*(L2B+(L2Bsize-n)) = *L2Ls;
--n;
++(L2Ls);
--(L2Ln);
if (L2Ln == 0) goto finish2_left;
} else {
*(L2B+(L2Bsize-n)) = *L2Rs;
--n;
++(L2Rs);
--(L2Rn);
if (L2Rn == 0) goto finish2_right;
}
}
buffer_count[I] = L2Bsize;
check_input(L2B, L2Bsize);
return;
finish2_left:
debug()<<"left drained, Ln="<<L2Ln<<", Rn="<<L2Rn<<"\n";
std::copy(L2Rs, L2Rs+n, L2B+L2Bsize-n);
L2Rn -= n;
L2Rs += n;
buffer_count[I] = L2Bsize;
check_input(L2B, L2Bsize);
return;
finish2_right:
debug()<<"right drained, Ln="<<L2Ln<<", Rn="<<L2Rn<<"\n";
std::copy(L2Ls, L2Ls+n, L2B+L2Bsize-n);
L2Ln -= n;
L2Ls += n;
buffer_count[I] = L2Bsize;
check_input(L2B, L2Bsize);
return;
}
}
template <unsigned K,unsigned I,template <unsigned,unsigned> class BufferLayout>
static inline __attribute__((always_inline))
void fill_inner(boost::array<Stream,K>&, unsigned char**,
boost::array<size_t,K>&, boost::false_type) {}
template <unsigned K,unsigned I,template <unsigned,unsigned> class BufferLayout>
static inline __attribute__((always_inline))
void fill_inner(boost::array<Stream,K>& restrict streams,
unsigned char** restrict buffer,
boost::array<size_t,K>& restrict buffer_count,
boost::true_type)
{
debug() << __func__ << ", inner, I="<<I<<"\n"; debug_indent;
size_t need = buffer_size<K,I/2>::value;
while (true) {
if (buffer_count[2*I]==0) {
fill<K,2*I,BufferLayout>()(streams,buffer,buffer_count);
if (buffer_count[2*I]==0) goto finish_left;
}
if (buffer_count[2*I+1]==0) {
fill<K,2*I+1,BufferLayout>()(streams, buffer,
buffer_count);
if (buffer_count[2*I+1]==0) goto finish_right;
}
const size_t lcount = buffer_count[2*I];
const size_t rcount = buffer_count[2*I+1];
unsigned char** l = buffer + BufferLayout<K,I>::lindex +
(buffer_size<K,I>::value - lcount);
unsigned char** r = buffer + BufferLayout<K,I>::rindex +
(buffer_size<K,I>::value - rcount);
unsigned char** resultbuf = buffer +
(I%2==0?BufferLayout<K,I/2>::lindex :
BufferLayout<K,I/2>::rindex) +
(buffer_size<K,I/2>::value - need);
debug()<<"L:'"<<*l<<"', R:'"<<*r<<"'\n";
if (cmp(*l, *r) <= 0) {
*resultbuf = *l;
--buffer_count[2*I];
} else {
*resultbuf = *r;
--buffer_count[2*I+1];
}
check_input(buffer+(I%2==0?BufferLayout<K,I/2>::lindex:
BufferLayout<K,I/2>::rindex),
buffer_size<K,I/2>::value-need);
if (--need==0) {
debug()<<"Done, buffer filled\n";
buffer_count[I] = buffer_size<K,I/2>::value;
return;
}
}
finish_left:
debug()<<"left stream drained\n";
while (true) {
if (buffer_count[2*I+1]==0) {
fill<K,2*I+1,BufferLayout>()(streams, buffer,
buffer_count);
if (buffer_count[2*I+1]==0) {
debug()<<"both streams prematurely drained\n";
unsigned char** begin = buffer +
(I%2==0?BufferLayout<K,I/2>::lindex :
BufferLayout<K,I/2>::rindex);
unsigned char** newbegin = buffer +
(I%2==0?BufferLayout<K,I/2>::lindex :
BufferLayout<K,I/2>::rindex) +
need;
memmove(newbegin, begin,
(buffer_size<K,I/2>::value - need)
* sizeof(unsigned char*));
buffer_count[I]=buffer_size<K,I/2>::value-need;
check_input(newbegin, buffer_count[I]);
return;
}
}
const size_t rcount = buffer_count[2*I+1];
unsigned char** r = buffer + BufferLayout<K,I>::rindex +
(buffer_size<K,I>::value - rcount);
unsigned char** resultbuf = buffer +
(I%2==0?BufferLayout<K,I/2>::lindex :
BufferLayout<K,I/2>::rindex) +
(buffer_size<K,I/2>::value - need);
*resultbuf = *r;
--buffer_count[2*I+1];
if (--need==0) {
debug()<<"\tbuffer filled\n";
buffer_count[I] = buffer_size<K,I/2>::value;
check_input(buffer+(I%2==0?BufferLayout<K,I/2>::lindex :
BufferLayout<K,I/2>::rindex), buffer_count[I]);
return;
}
}
finish_right:
debug()<<"right stream drained\n";
while (true) {
if (buffer_count[2*I]==0) {
fill<K,2*I,BufferLayout>()(streams,buffer,buffer_count);
if (buffer_count[2*I]==0) {
debug()<<"both streams prematurely drained\n";
unsigned char** begin = buffer +
(I%2==0?BufferLayout<K,I/2>::lindex :
BufferLayout<K,I/2>::rindex);
unsigned char** newbegin = buffer +
(I%2==0?BufferLayout<K,I/2>::lindex :
BufferLayout<K,I/2>::rindex) +
need;
memmove(newbegin, begin,
(buffer_size<K,I/2>::value - need)
* sizeof(unsigned char*));
buffer_count[I]=buffer_size<K,I/2>::value-need;
check_input(newbegin, buffer_count[I]);
return;
}
}
const size_t lcount = buffer_count[2*I];
unsigned char** l = buffer + BufferLayout<K,I>::lindex +
(buffer_size<K,I>::value - lcount);
unsigned char** resultbuf = buffer +
(I%2==0?BufferLayout<K,I/2>::lindex :
BufferLayout<K,I/2>::rindex) +
(buffer_size<K,I/2>::value - need);
*resultbuf = *l;
--buffer_count[2*I];
if (--need==0) {
debug()<<"buffer filled\n";
buffer_count[I] = buffer_size<K,I/2>::value;
check_input(buffer+(I%2==0?BufferLayout<K,I/2>::lindex :
BufferLayout<K,I/2>::rindex), buffer_count[I]);
return;
}
}
}
template <unsigned K, template <unsigned, unsigned> class BufferLayout>
static inline __attribute__((always_inline))
void fill_root(boost::array<Stream,K>& restrict streams,
unsigned char** restrict result,
unsigned char** restrict buffer,
boost::array<size_t,K>& restrict buffer_count)
{
debug() << __func__ << ", root\n"; debug_indent;
while (true) {
if (buffer_count[2]==0) {
fill<K,2,BufferLayout>()(streams, buffer, buffer_count);
if (buffer_count[2]==0) { goto finish_left; }
}
if (buffer_count[3]==0) {
fill<K,3,BufferLayout>()(streams, buffer, buffer_count);
if (buffer_count[3]==0) { goto finish_right; }
}
const size_t lcount = buffer_count[2];
const size_t rcount = buffer_count[3];
unsigned char** l = buffer + BufferLayout<K,1>::lindex +
(buffer_size<K,1>::value - lcount);
unsigned char** r = buffer + BufferLayout<K,1>::rindex +
(buffer_size<K,1>::value - rcount);
debug()<<"L:'"<<*l<<"', R:'"<<*r<<"'\n";
if (cmp(*l, *r) <= 0) {
*(result++) = *l;
--buffer_count[2];
} else {
*(result++) = *r;
--buffer_count[3];
}
}
finish_left:
debug()<<"left stream drained\n";
assert(buffer_count[2] == 0);
assert(buffer_count[3] != 0);
while (true) {
const size_t num = buffer_count[3];
if (num==0) { return; }
unsigned char** begin = buffer + BufferLayout<K,1>::rindex +
(buffer_size<K,1>::value - num);
std::copy(begin, begin+num, result);
result += num;
buffer_count[3] = 0;
fill<K,3,BufferLayout>()(streams, buffer, buffer_count);
}
assert(0);
finish_right:
debug()<<"right stream drained\n";
assert(buffer_count[2] != 0);
assert(buffer_count[3] == 0);
while (true) {
const size_t num = buffer_count[2];
if (num==0) { return; }
unsigned char** begin = buffer + buffer_size<K,1>::value - num;
std::copy(begin, begin+num, result);
result += num;
buffer_count[2] = 0;
fill<K,2,BufferLayout>()(streams, buffer, buffer_count);
}
assert(0);
}
template <unsigned K,unsigned I,template <unsigned,unsigned> class BufferLayout>
struct fill
{
void operator()(boost::array<Stream,K>& restrict streams,
unsigned char** restrict buffer,
boost::array<size_t,K>& restrict buffer_count) const
{
fill_inner<K,I,BufferLayout>(streams, buffer, buffer_count,
typename boost::integral_constant<bool, (I>1 && I<K/2)>());
fill_leaf<K,I,BufferLayout>(streams, buffer, buffer_count,
typename boost::integral_constant<bool, (I>=K/2 && I<K)>());
}
};
#ifdef FUNNELSORT_EXTRA_INLINING
template <unsigned I,template <unsigned,unsigned> class BufferLayout>
struct fill<8,I,BufferLayout>
{
enum { K=8 };
void operator()(boost::array<Stream,K>& restrict streams,
unsigned char** restrict buffer,
boost::array<size_t,K>& restrict buffer_count) const
__attribute__((always_inline))
{
fill_inner<K,I,BufferLayout>(streams, buffer, buffer_count,
typename boost::integral_constant<bool, (I>1 && I<K/2)>());
fill_leaf<K,I,BufferLayout>(streams, buffer, buffer_count,
typename boost::integral_constant<bool, (I>=K/2 && I<K)>());
}
};
template <unsigned I,template <unsigned,unsigned> class BufferLayout>
struct fill<16,I,BufferLayout>
{
enum { K=16 };
void operator()(boost::array<Stream,K>& restrict streams,
unsigned char** restrict buffer,
boost::array<size_t,K>& restrict buffer_count) const
__attribute__((always_inline))
{
fill_inner<K,I,BufferLayout>(streams, buffer, buffer_count,
typename boost::integral_constant<bool, (I>1 && I<K/2)>());
fill_leaf<K,I,BufferLayout>(streams, buffer, buffer_count,
typename boost::integral_constant<bool, (I>=K/2 && I<K)>());
}
};
#endif
template <unsigned K, template <unsigned, unsigned> class BufferLayout>
static void
funnelsort(unsigned char** strings, size_t n, unsigned char** restrict tmp)
{
debug() << __func__ << "(), n=" << n << "\n";
if (n < 32) {
insertion_sort(strings, n, 0);
return;
}
size_t splitter = n/K;
boost::array<Stream, K> streams;
streams[0].stream = strings;
streams[0].n = splitter;
for (unsigned i=1; i < K-1; ++i) {
streams[i].stream = streams[i-1].stream + splitter;
streams[i].n = splitter;
}
streams[K-1].stream = streams[K-2].stream + splitter;
streams[K-1].n = n - (K-1)*splitter;
for (unsigned i=0; i < K; ++i) {
funnelsort<(K>16?K/4:K/2),BufferLayout>(streams[i].stream,
streams[i].n, tmp);
check_input(streams[i].stream, streams[i].n);
}
boost::array<unsigned char*, buffer_total_size<K>::value> buffers;
boost::array<size_t, K> buffer_count;
buffer_count.assign(0);
fill_root<K,BufferLayout>(streams,tmp,buffers.c_array(),buffer_count);
(void) memcpy(strings, tmp, n*sizeof(unsigned char*));
check_input(strings, n);
}
void mergesort_4way(unsigned char**, size_t, unsigned char**);
template <> void funnelsort<2,buffer_layout_bfs>(unsigned char** strings,
size_t n, unsigned char** tmp) { mergesort_4way(strings, n, tmp); }
template <> void funnelsort<4,buffer_layout_bfs>(unsigned char** strings,
size_t n, unsigned char** tmp) { mergesort_4way(strings, n, tmp); }
template <> void funnelsort<2,buffer_layout_dfs>(unsigned char** strings,
size_t n, unsigned char** tmp) { mergesort_4way(strings, n, tmp); }
template <> void funnelsort<4,buffer_layout_dfs>(unsigned char** strings,
size_t n, unsigned char** tmp) { mergesort_4way(strings, n, tmp); }
template <unsigned K, template <unsigned, unsigned> class BufferLayout>
void funnelsort_Kway(unsigned char** strings, size_t n)
{
unsigned char** tmp = static_cast<unsigned char**>(
malloc(n*sizeof(unsigned char*)));
funnelsort<K, BufferLayout>(strings, n, tmp);
free(tmp);
}
void funnelsort_8way_bfs(unsigned char** strings, size_t n)
{ funnelsort_Kway<8, buffer_layout_bfs>(strings, n); }
void funnelsort_16way_bfs(unsigned char** strings, size_t n)
{ funnelsort_Kway<16, buffer_layout_bfs>(strings, n); }
void funnelsort_32way_bfs(unsigned char** strings, size_t n)
{ funnelsort_Kway<32, buffer_layout_bfs>(strings, n); }
void funnelsort_64way_bfs(unsigned char** strings, size_t n)
{ funnelsort_Kway<64, buffer_layout_bfs>(strings, n); }
void funnelsort_128way_bfs(unsigned char** strings, size_t n)
{ funnelsort_Kway<128, buffer_layout_bfs>(strings, n); }
void funnelsort_8way_dfs(unsigned char** strings, size_t n)
{ funnelsort_Kway<8, buffer_layout_dfs>(strings, n); }
void funnelsort_16way_dfs(unsigned char** strings, size_t n)
{ funnelsort_Kway<16, buffer_layout_dfs>(strings, n); }
void funnelsort_32way_dfs(unsigned char** strings, size_t n)
{ funnelsort_Kway<32, buffer_layout_dfs>(strings, n); }
void funnelsort_64way_dfs(unsigned char** strings, size_t n)
{ funnelsort_Kway<64, buffer_layout_dfs>(strings, n); }
void funnelsort_128way_dfs(unsigned char** strings, size_t n)
{ funnelsort_Kway<128, buffer_layout_dfs>(strings, n); }
CONTESTANT_REGISTER(funnelsort_8way_bfs,
"rantala/funnelsort_8way_bfs",
"funnelsort 8way bfs")
CONTESTANT_REGISTER(funnelsort_16way_bfs,
"rantala/funnelsort_16way_bfs",
"funnelsort 16way bfs")
CONTESTANT_REGISTER(funnelsort_32way_bfs,
"rantala/funnelsort_32way_bfs",
"funnelsort 32way bfs")
CONTESTANT_REGISTER(funnelsort_64way_bfs,
"rantala/funnelsort_64way_bfs",
"funnelsort 64way bfs")
CONTESTANT_REGISTER(funnelsort_128way_bfs,
"rantala/funnelsort_128way_bfs",
"funnelsort 128way bfs")
CONTESTANT_REGISTER(funnelsort_8way_dfs,
"rantala/funnelsort_8way_dfs",
"funnelsort 8way dfs")
CONTESTANT_REGISTER(funnelsort_16way_dfs,
"rantala/funnelsort_16way_dfs",
"funnelsort 16way dfs")
CONTESTANT_REGISTER(funnelsort_32way_dfs,
"rantala/funnelsort_32way_dfs",
"funnelsort 32way dfs")
CONTESTANT_REGISTER(funnelsort_64way_dfs,
"rantala/funnelsort_64way_dfs",
"funnelsort 64way dfs")
CONTESTANT_REGISTER(funnelsort_128way_dfs,
"rantala/funnelsort_128way_dfs",
"funnelsort 128way dfs")
}