假设我有一个数组
k = [1 2 0 0 5 4 0]
我可以按如下方式计算掩码
m = k > 0 = [1 1 0 0 1 1 0]
仅使用蒙版m和以下操作
我可以将k压缩成以下内容
[1 2 5 4]
以下是我目前的工作方式(MATLAB伪代码):
function out = compact( in )
d = in
for i = 1:size(in, 2) %do (# of items in in) passes
m = d > 0
%shift left, pad w/ 0 on right
ml = [m(2:end) 0] % shift
dl = [d(2:end) 0] % shift
%if the data originally has a gap, fill it in w/ the
%left shifted one
use = (m == 0) & (ml == 1) %2 comparison
d = use .* dl + ~use .* d
%zero out elements that have been moved to the left
use_r = [0 use(1:end-1)]
d = d .* ~use_r
end
out = d(1 : size(find(in > 0), 2)) %truncate the end
end
直觉
每次迭代,我们将掩模向左移动并比较掩模。如果我们发现在此移位之后,原始为void(mask [i] = 0)的索引现在有效(mask [i] = 1),我们设置索引以获得左移数据。
问题
上述算法具有O(N *(3班+2比较+ AND +加+ 3倍))。有没有办法提高效率?
答案 0 :(得分:10)
原始伪代码中没有太多优化。我在这里看到了一些小改进:
use = (m == 0) & (ml == 1)
可能会简化为use = ~m & ml
,~
视为单独操作,最好使用倒置形式:use = m | ~ml
,d = ~use .* dl + use .* d
,use_r = [1 use(1:end-1)]
,d = d .*use_r
但是有可能发明更好的算法。算法的选择取决于所使用的CPU资源:
C ++,64位,子集宽度= 8:
typedef unsigned long long ull;
const ull h = 0x8080808080808080;
const ull l = 0x0101010101010101;
const ull end = 0xffffffffffffffff;
// uncompacted bytes
ull x = 0x0100802300887700;
// set hi bit for zero bytes (see D.Knuth, volume 4)
ull m = h & ~(x | ((x|h) - l));
// bitmask for nonzero bytes
m = ~(m | (m - (m>>7)));
// tail zero bytes need no special treatment
m |= (m - 1);
while (m != end)
{
ull tailm = m ^ (m + 1); // bytes to be processed
ull tailx = x & tailm; // get the bytes
tailm |= (tailm << 8); // shift 1 byte at a time
m |= tailm; // all processed bytes are masked
x = (x ^ tailx) | (tailx << 8); // actual byte shift
}
答案 1 :(得分:5)
所以你需要弄清楚额外的并行性,转移/改组开销是否值得这么简单的任务。
for(int inIdx = 0, outIdx = 0; inIdx < inLength; inIdx++) {
if(mask[inIdx] == 1) {
out[outIdx] = in[inIdx];
outIdx++;
}
}
如果你想进行并行SIMD路由,你最好的选择是一个SWITCH CASE,其中包含掩码的下4位的所有可能的排列。为什么不是8?因为PSHUFD指令只能在XMMX m128而不是YMMX m256上进行随机播放。
所以你做了16个案例:
因此每种情况都是最少量的处理(1到2个SIMD指令和1个输出指针添加)。 case语句的周围循环将处理常量输入指针加法(加4)和MOVDQA加载输入。
答案 2 :(得分:3)
原始代码一次只移动数组元素一步。这可以改进。可以对数组元素进行分组,并将它们一次移位2 ^ k步。
该算法的第一部分计算每个元素应移位的步数。第二部分移动元素 - 首先是一步,然后是2,然后是4等。这样可以正常工作,元素不会混合,因为每次移动后有足够的空间来执行2倍大的移位。
Matlab,未经测试的代码:
function out = compact( in )
m = in <= 0
for i = 1:size(in, 2)-1
m = [0 m(1:end-1)]
s = s + m
end
d = in
shift = 1
for j = 1:ceil(log2(size(in, 2)))
s1 = rem(s, 2)
s = (s - s1) / 2
d = (d .* ~s1) + ([d(1+shift:end) zeros(1,shift)] .* [s1(1+shift:end) zeros(1,shift)])
shift = shift*2
end
out = d
end
上述算法的复杂度为O(N *(1 shift + 1 add)+ log(N)*(1 rem + 2 add + 3 mul + 2 shift))。
答案 3 :(得分:1)
读取原始问题下面的注释,在实际问题中,数组包含32位浮点数,并且掩码是(一个?)32位整数,所以我不明白为什么移位等应该用于压缩阵列。简单的压缩算法(在C中)将是这样的:
float array[8];
unsigned int mask = ...;
int a = 0, b = 0;
while (mask) {
if (mask & 1) { array[a++] = array[b]; }
b++;
mask >>= 1;
}
/* Size of compacted array is 'a' */
/* Optionally clear the rest: */
while (a < 8) array[a++] = 0.0;
微小的变化可能是由于掩码的位顺序,但唯一需要的ALU操作是索引变量更新和移位和ANDing掩码。因为原始数组的宽度至少为256位,所以没有普通的CPU可以按位逐位移动整个数组。
答案 4 :(得分:0)
假设你想要的是只用C ++中的最小步骤来存储数组中的正整数,这是一个示例代码:
int j = 0;
int arraysize = (sizeof k)/4;
int store[arraysize];
for(int i = 0; i<arraysize; i++)
{
if(k[i] > 0)
{
store[j] = k[i];
j++;
}
}
如果您不想使用for
循环,也可以直接使用 k [] 的元素。