我有两个巨大的排序数组(每个约100K项)。我需要将它们相交非常快。现在我以标准的方式做到了:
但完成时间太长(约350微秒),导致整体性能相当差。有没有办法更快地做到这一点?
P.S。交叉口尺寸不大于1000件(平均而言),我只需要25到100件。
答案 0 :(得分:2)
并行运行2个100k阵列需要大约200k的比较。您目前正在以350微秒= 350k纳秒的速度完成它。所以你的每个比较时间不到2纳秒。如果你的CPU大约是4 GHz,那就是8个时钟周期。
那很好。你可以尝试复杂,检测跑步等等,但是你可能会因为管道失速而伤害自己,而不是拯救你的工作。
只有两种方法可以加快速度。减少工作量,或增加更多工人。
你已经表明减少工作是可行的,这就是为什么Tamas Hegedus建议这样做的原因。而不是创建交集,而是创建一个Iterator
,它将返回交叉点中的下一个东西。这将要求您重写使用所述迭代器的逻辑,但是您将在当前计算的10%以下完成。这将快接近10倍。
至于添加工作者,你会想要在工作线程之间划分工作,并防止他们互相踩踏。对于k
小(不大于你的CPU数量!),对数量的对数工作量,您可以快速选择查找将组合数组分解为的k-1
值k
甚至是块( oops 适应http://www.geeksforgeeks.org/median-of-two-sorted-arrays/而不是快速选择...),以及每个数组中这些值的索引。这会产生甚至困难的k
个问题,每个问题都可以指定为4个数字。旋转k
个线程,让每个线程得到答案的一部分。这将比您目前的速度快k
倍。
以批次更多的努力为代价,可以组合这些方法。你做的是让迭代器创建4个工人,然后分配给每个工人。当你调用iter.next()
时,迭代器会给你一个下一个值,如果它有一个值。如果它没有一个它将等待正在生成其下一个块的工人完成,抓住该块,如果一个准备好,则将该工作交给另一个块,然后分发该块中的第一个值。您可以使用块大小。你想要它足够大,以至于CPU很好地确定它应该从RAM流到CPU缓存,并且不认为线程之间存在同步争用。
我的猜测考虑到大小和同步限制,混合方法对于迭代器方法来说不会是一场胜利,如果有的话。但如果你真的很绝望,你可以尝试一下。
答案 1 :(得分:1)
我发布了一个问题/解决方案的天真实现:2个数组填充随机整数。如果达到100个相交值的阈值,则循环中断。
使用OP逻辑进行一次循环。另一个启动两个线程,每个线程处理一半的数组。
似乎线程开销可能是一个问题。或者它可能需要微调。
这是20次运行的样本。最糟糕的情况:没有交叉点强制运行到数组的末尾。时间以微秒为单位。
Workers: 2806
Workers: 4197
Workers: 4235
Workers: 818
Workers: 729
Workers: 3376
Workers: 740
Workers: 688
Workers: 2245
Workers: 732
Workers: 330
Workers: 945
Workers: 605
Workers: 630
Workers: 630
Workers: 334
Workers: 643
Workers: 309
Workers: 290
Workers: 761
done
Sorted: 1525
Sorted: 405
Sorted: 550
Sorted: 880
Sorted: 265
Sorted: 267
Sorted: 252
Sorted: 310
Sorted: 253
Sorted: 272
Sorted: 285
Sorted: 270
Sorted: 270
Sorted: 315
Sorted: 267
Sorted: 269
Sorted: 265
Sorted: 258
Sorted: 269
Sorted: 289
done
package so;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public final class CrazyClass {
static class Feeder implements Runnable{
final int b, e;
int[] k1001;
int[] k1002;
final Set<Integer> setThis;
Feeder(int[] ia, int[] ia1, int be, int en, Set<Integer> s){
k1001 = ia;
k1002= ia1;
b = be;
e = en;
setThis = s;
}
public void run() {
int i2 = b;
for(int i1 = b; i1 < e; i1++){
if (k1001[i1] == k1002[i2]){
synchronized(setThis){
setThis.add(k1001[i1]);
if (setThis.size() == 25){
System.out.println("bye!!!");
break;
}
}
}
else if (k1001[i1] < k1002[i2])
i1++;
else if (k1001[i1] > k1002[i2])
i2++;
}
}
}
static void sorted(){
int i1 = 0, i2 = 0;
Set<Integer> result = new HashSet<Integer>();
Random r = new Random();
int[] k1001 = new int[100000];
int[] k1002 = new int[100000];
for(int i = 0; i< k1001.length; i++){
k1001[i] = r.nextInt();
k1002[i] = r.nextInt();
}
Arrays.sort(k1001);
Arrays.sort(k1002);
long l = System.nanoTime();
for(; i1 < k1001.length; i1++){
if (k1001[i1] == k1002[i2]){
result.add(k1001[i1]);
if (result.size() == 100){
System.out.println("bye!!!");
break;
}
}
else if (k1001[i1] < k1002[i2])
i1++;
else if (k1001[i1] > k1002[i2])
i2++;
}
l = System.nanoTime() - l;
System.out.println("Sorted: " + TimeUnit.MICROSECONDS.convert(l, TimeUnit.NANOSECONDS));
}
static void workers(){
Thread t1, t2;
Set<Integer> setThis = new HashSet<Integer>();
Random r = new Random();
int[] k1001 = new int[100000];
int[] k1002 = new int[100000];
for(int i = 0; i< k1001.length; i++){
k1001[i] = r.nextInt();
k1002[i] = r.nextInt();
}
t1 = new Thread(new Feeder(k1001, k1002, 0, 49999, setThis));
t2 = new Thread(new Feeder(k1001, k1002, 50000, 99999, setThis));
try{
long l = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Workers: " + TimeUnit.MICROSECONDS.convert(System.nanoTime() - l, TimeUnit.NANOSECONDS));
}catch(Exception x){
}
}
static public void main(String[] args){
int run = 20;
for(int i = 0; i < run; i++)
workers();
System.out.println("done");
for(int i = 0; i < run; i++)
sorted();
System.out.println("done");
}
}
答案 2 :(得分:0)
以下代码对我来说在10毫安左右。所以我猜你要么处理字符串,要么使用脚本语言。
package com.example.so.algorithms;
import java.util.Arrays;
import java.util.Random;
/**
* <p> http://stackoverflow.com/questions/42538902/how-to-intersect-two-sorted-arrays-the-fastest-possible-way#comment72213844_42538902 </p>
* <p> Given two sorted sub-lists of 100k each determine the first 10 intersecting (common) entries within 350 millis </p>
* @author Ravindra
* @since 03March2017
*
*/
public class TestMergeIntersection {
/**
* <pre>
Time (millis):9
Result :[442958664, 932132404, 988442487, 1356502780, 1614742980, 1923995812, 1985016181, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
</pre>
* @param args
*/
public static void main(String[] args) {
handleTest();
}
private static void handleTest() {
int size = 1024*128;
int intersectionCount = 100;
int[] arrayOne = generateSortedSublist(size);
int[] arrayTwo = generateSortedSublist(size);
int[] result = new int[intersectionCount];
int count = 0;
int i=0;
int j=0;
long start = System.currentTimeMillis();
while(count < 100 && i < size && j < size ) {
if( arrayOne[i] < arrayTwo[j]) {
i++;
} else if( arrayOne[i] > arrayTwo[j] ) {
j++;
} else {
result[count] =arrayOne[i];
i++;
j++;
count++;
}
}
long end = System.currentTimeMillis();
System.out.println("Time (millis):"+(end-start));
System.out.println("Result :"+Arrays.toString(result));
}
private static int[] generateSortedSublist(int size) {
Random random = new Random();
int[] result = new int[size];
for(int i=0;i<result.length;i++) {
result[i] = random.nextInt(Integer.MAX_VALUE);
}
Arrays.sort(result);
return result;
}
}