我一直试图找出最有效的方法,许多线程在位级别上改变一个非常大的字节数组。为了便于解释,我将围绕一个多线程的Eratosthenes筛子提出问题,以便于解释这个问题。虽然我不会完全完成代码,因为我将省略某些与之无直接关系的部分。筛子也不会完全优化,因为这不是直接的问题。筛子将以这样的方式工作,即它保存哪个值是字节数组中的素数,其中每个字节包含7个数字(由于所有被签名的东西,我们不能改变第一位)。
让我们说我们的目标是找到低于1 000 000 000(10亿)的所有素数。因此,我们需要一个长度为1 000 000 000/7 +1或142 857 143(约1.43亿)的字节数组。
class Prime {
int max = 1000000000;
byte[] b = new byte[(max/7)+1];
Prime() {
for(int i = 0; i < b.length; i++) {
b[i] = (byte)127; //Setting all values to 1 at start
}
findPrimes();
}
/*
* Calling remove will set the bit value associated with the number
* to 0 signaling that isn't an prime
*/
void remove(int i) {
int j = i/7; //gets which array index to access
b[j] = (byte) (b[j] & ~(1 << (i%7)));
}
void findPrimes() {
remove(1); //1 is not a prime and we wanna remove it from the start
int prime = 2;
while (prime*prime < max) {
for(int i = prime*2; i < max; i = prime + i) {
remove(i);
}
prime = nextPrime(prime); //This returns the next prime from the list
}
}
... //Omitting code, not relevant to question
}
现在我们得到了一个基本的大纲,其中某些内容贯穿某个多重复制表的所有数字,如果我们发现它们不是素数,则调用remove来删除符合数字的数字设置位。
现在我们创建线程来检查我们。我们拆分工作,以便每个人从表中删除一部分。因此,例如,如果我们有4个线程并且我们正在通过乘法表运行素数2,我们将在8次表中分配线程1全部,起始偏移量为2,即4,10,18,... 。,第二个线程的偏移量为4,因此它会经过6,14,22 ......等等。然后他们会调用他们想要的删除。
现在回答真正的问题。因为大多数人都可以看到,当素数小于7时,我们将有多个线程访问相同的数组索引。例如,在运行2时,我们将有线程1,线程2和线程3都将尝试访问b [0]以更改导致我们不想要的竞争条件的字节。
因此,问题是,优化对字节数组的访问的最佳方法是什么。
到目前为止,我对它的想法是:
synchronized
放在remove方法上。这显然很容易实现,但这是一个可怕的想法,因为它可以消除线程中的任何类型的收益。在没有过度使用内存的情况下,还有其他方法可以快速,最佳地完成此操作吗?
(这是我在这里的第一个问题,所以我尽量详细而详尽,但我会接受任何关于如何改进问题的评论 - 更多细节,需要更多细节等。)
答案 0 :(得分:5)
您可以使用原子整数数组。不幸的是,没有getAndAND
,这对于您的remove()
函数来说是理想的,但您可以循环使用CAS:
java.util.concurrent.atomic.AtomicIntegerArray aia;
....
void remove(int i) {
int j = i/32; //gets which array index to access
do {
int oldVal = aia.get(j);
int newVal = oldVal & ~(1 << (i%32));
boolean updated = aia.weakCompareAndSet(j, oldVal, newVal);
} while(!updated);
}
基本上你一直在尝试调整插槽以移除那个位,但只有在没有其他人从你下面修改它时你才能成功。安全,而且可能非常有效。 weakCompareAndSet
基本上是一个抽象的Load-link/Store conditional指令。
答案 1 :(得分:1)
我认为你可以避免同步线程...
例如,此任务:
for(int i = prime*2; i < max; i = prime + i) {
remove(i);
}
它可以在小任务中进行分区。
for (int i =0; i < thread_poll; i++){
int totalPos = max/8; // dividing virtual array in bytes
int partitionSize = totalPos /thread_poll; // dividing bytes by thread poll
removeAll(prime, partitionSize*i*8, (i + 1)* partitionSize*8);
}
....
//没有比赛!!!
void removeAll(int prime, int initial; int max){
k = initial / prime;
if (k < 2) k = 2;
for(int i = k * prime; i < max; i = i + prime) {
remove(i);
}
}