这是我最近在谷歌采访中提出的问题,我提供了一个涉及位移的答案,并且是 O(n),但她说这不是最快的方法。我不明白,有没有办法计算所设置的位而不必遍历提供的整个位?
5 回答
蛮力:10000 * 16 * 4 = 640,000 次操作。(每个 16 位字的移位、比较、递增和迭代)
更快的方式:
我们可以建立表 00-FF -> 设置的位数。256 * 8 * 4 = 8096 次操作
即,我们建立一个表格,我们为每个字节计算一组位。
然后对于每个 16 位 int 我们将其拆分为上和下
for (n in array)
byte lo = n & 0xFF; // lower 8-bits
byte hi = n >> 8; // higher 8-bits
// simply add number of bits in the upper and lower parts
// of each 16-bits number
// using the pre-calculated table
k += table[lo] + table[hi];
}
迭代中总共有 60000 次操作。即总共 68096 次操作。虽然它是 O(n),但常数较小(约少 9 倍)。
换句话说,我们计算每个 8 位数字的位数,然后将每个 16 位数字拆分为两个 8 位,以便计算使用预建表设置的位。
(几乎)总是有更快的方法。阅读有关查找表的信息。
当被问到这个问题时,我不知道正确答案是什么,但我相信今天解决这个问题最明智的方法是使用POPCNT
指令。具体来说,您应该使用64 位版本。由于我们只需要设置位的总数,因此我们不关心 16 位元素之间的边界。由于 32 位和 64 位POPCNT
指令同样快,因此您应该使用 64 位版本来计算每个周期的四个元素的位数。
我刚刚用Java实现了它:
import java.util.Random;
public class Main {
static int array_size = 1024;
static int[] array = new int[array_size];
static int[] table = new int[257];
static int total_bits_in_the_array = 0;
private static void create_table(){
int i;
int bits_set = 0;
for (i = 0 ; i <= 256 ; i++){
bits_set = 0;
for (int z = 0; z <= 8 ; z++){
bits_set += i>>z & 0x1;
}
table[i] = bits_set;
//System.out.println("i = " + i + " bits_set = " + bits_set);
}
}
public static void main(String args[]){
create_table();
fill_array();
parse_array();
System.out.println("The amount of bits in the array is: " + total_bits_in_the_array);
}
private static void parse_array() {
int current;
for (int i = 0; i < array.length; i++){
current = array[i];
int down = current & 0xff;
int up = current & 0xff00;
int sum = table[up] + table[down];
total_bits_in_the_array += sum;
}
}
private static void fill_array() {
Random ran = new Random();
for (int i = 0; i < array.length; i++){
array[i] = Math.abs(ran.nextInt()%512);
}
}
}
也在https://github.com/leitao/bits-in-a-16-bits-integer-array/blob/master/Main.java
您可以预先计算以字节为单位的位计数,然后将其用于查找。如果您做出某些假设,它会更快。
操作数(仅计算,不读取输入)应采用以下
换档方法:
对于每个字节:2 次操作(移位、加法)乘以 16 位 = 32 次操作,0 次内存访问次数 10000 = 320 000 次操作 + 0 次内存访问
预计算方法:
255 次 2 次操作(移位、加法)乘以 8 位 = 4080 次操作 + 255 次内存访问(写入结果)
对于每个字节:2 ops(计算地址)+ 2 mem 访问 + op(添加结果)= 30 000 ops + 20 000 mem 访问
总计 30 480 个操作 + 20 255 个内存访问
所以更多的内存访问和更少的操作
因此,假设其他一切都相同,如果我们可以假设内存访问比操作快 (320 000 - 30 480)/20 255 = 14.29 倍,那么 10 000 字节的预计算会更快
如果您独自在一个相当现代的盒子上的专用内核上,这可能是正确的,因为 255 字节应该适合缓存。如果您开始出现缓存未命中,则该假设可能不再成立。
此外,该数学假设指针算术和直接内存访问以及原子操作和原子内存访问。根据您选择的语言(显然,根据之前的答案,您选择的编译器切换),该假设可能不成立。
最后,如果您考虑可扩展性,事情会变得更有趣:移位可以轻松地并行化到多达 10000 个内核上,但不一定要进行预计算。然而,随着字节数的增加,查找变得越来越有利。
所以,简而言之。是的,在相当合理的假设下,预计算会更快,但不,不能保证会更快。