在Java中对文本文件进行数学计算的性能

4
我正在处理一个包含约60,000个点坐标的文本文件(我预计很快会扩大规模),并对每个点到其他每个点执行马氏距离,然后将结果输出为文本文件。这意味着我的结果将近3,600,000,000行长。我的程序每1或2秒创建约60,000行。
我认为我的代码无法进行多线程处理,这种算法有更好的编码方式吗?人们如何处理这些进程?
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

    public class Coord {
        public int a,b,c,d,e,f;


    public static void main(String[] args) throws IOException {
        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("/Users/evanlivingston/2a.txt", true)));
        Scanner sc = new Scanner(new File("/Users/evanlivingston/1.txt"));
        List<Coord> coords = new ArrayList<Coord>();{


            // for each line in the file
            while(sc.hasNextLine()) {
                String[] numstrs = sc.nextLine().split("\\s+"); 

                Coord c = new Coord();


                c.a = Integer.parseInt(numstrs[1]);
                c.b = Integer.parseInt(numstrs[2]);
                c.c = Integer.parseInt(numstrs[3]);
                c.d = Integer.parseInt(numstrs[4]);
                c.e = Integer.parseInt(numstrs[5]);
                c.f = Integer.parseInt(numstrs[6]);

                coords.add(c);

            }


// now you have all coords in memory
    int counter = 0;        {
for(int i=0; i<coords.size(); i++ ) 
    for( int j=0; j<coords.size(); j++, counter++ ) 
    {
        Coord c1 = coords.get(i);
        Coord c2 = coords.get(j);
        double foo = ((c1.a - c2.a) * (c1.a - c2.a)) *1 ;
        double goo = ((c1.b - c2.b) * (c1.b - c2.b)) *1 ;
        double hoo = ((c1.c - c2.c) * (c1.c - c2.c)) *2 ;
        double joo = ((c1.d - c2.d) * (c1.d - c2.d)) *2 ;
        double koo = ((c1.e - c2.e) * (c1.e - c2.e)) *4 ;
        double loo = ((c1.f - c2.f) * (c1.f - c2.f)) *4 ;
        double zoo = Math.sqrt(foo + goo + hoo + joo + koo + loo);

        out.println(counter + "; " + i + " " + j + " " + zoo);
       System.out.println(counter + "; " + i + " " + j + " " + zoo);

    }
    out.flush();
    out.close();
            }
        }
    }   
}

我的输入文件长这样

0 0 0 0 0 0 0

1 0 0 0 0 0 1

....

59318 12 2 12 2 12 2

第一个数字是占位符。这是一个列表,其中包含替换组合的所有可能性,但受限于最后一行中所见的数量。

现在看起来计算需要大约16个小时,这仍然太长了。更不用说我估计最终文本输出将达到约120 GB。


这取决于你想做什么。如果你能提供更多关于你的目标的信息,我们可以帮助你改进你的代码。 - das_weezul
我打赌,三分之二的时间都花在解析和格式化数字上。如果你可以安排好读取二进制整数和写入二进制浮点数,你将会 a) 节省时间 b) 或许在输出文件中节省空间 c) 拥有一个能够随意访问特定结果的输出文件。 - Ingo
Integer.parseInt相当糟糕,你可以寻找更快的实现。 - Thomas Jungblut
Weezul,我正在尝试创建一个N个物体在K个维度中存在的所有可能配置列表,然后我想找到所有这些可能配置之间的距离。上面的代码是用于最后的处理过程。稍后我将处理数据的排序和管理,现在我只想执行计算。 - evanlivingston
你能否发布一份输入文件的小样本以供考虑? - user177800
请查看我下面更新的多线程代码。你的目标性能是什么?在1小时内完成吗?那就使用4个CPU的机器和一个非常快的磁盘驱动器(例如固态硬盘)。 - Vladimir Dyuzhev
3个回答

7
你的代码效率非常低。你在文件的每一行上都第二次读取文件(!!!)。磁盘IO非常慢。
你应该将文件加载到解析的内存结构(一个双精度数组)中,然后对其进行嵌套循环。
“我认为我的代码不能多线程运行,这是正确的吗?”你是错误的。这个任务会从线程中获得很大的好处。但你的首要任务是摆脱重复的IO。我猜测性能就足够好了。
更新到更新
重新编写了你的类,使用了多个线程(默认为4个)。缺点是输出文件中的行不按顺序写入,但如果需要,可以使用unix sort实用程序在计算后进行排序。A->B和B->A仍然被计算,因为我找不到一个简单的方法来存储A->B的结果,除非使用Java 64位并安装一些64G的RAM。
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Coord {
    public int a, b, c, d, e, f;

    private static class CoordsThread extends Thread {
        private int start;
        private int end;
        private List<Coord> coords;
        private PrintWriter out;

        public CoordsThread(int start, int end, List<Coord> list, PrintWriter out) {
            this.start = start;
            this.end = end;
            this.coords = list;
            this.out = out;

            // last block can be shorter
            if( this.end > this.coords.size() ) this.end = this.coords.size();
        }

        public void run() {
            System.out.println("started thread "+getName()+" for ["+start+";"+end+")");
            for (int i = start; i < end; i++) {
                for (int j = 0; j < coords.size(); j++ ) {
                    Coord c1 = coords.get(i);
                    Coord c2 = coords.get(j);
                    double foo = ((c1.a - c2.a) * (c1.a - c2.a)) * 1;
                    double goo = ((c1.b - c2.b) * (c1.b - c2.b)) * 1;
                    double hoo = ((c1.c - c2.c) * (c1.c - c2.c)) * 2;
                    double joo = ((c1.d - c2.d) * (c1.d - c2.d)) * 2;
                    double koo = ((c1.e - c2.e) * (c1.e - c2.e)) * 4;
                    double loo = ((c1.f - c2.f) * (c1.f - c2.f)) * 4;
                    double zoo = Math.sqrt(foo + goo + hoo + joo + koo + loo);

                    synchronized (out) {
                        out.println(i*coords.size()+j + "; " + i + " " + j + " " + zoo);
                    }
                }
            }
            System.out.println("completed thread "+getName());
        }
    }

    public static void main(String[] args) throws Exception {
        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("2.txt")));
        Scanner sc = new Scanner(new File("1.txt"));
        List<Coord> coords = new ArrayList<Coord>();

        // for each line in the file
        while (sc.hasNextLine()) {
            String[] numstrs = sc.nextLine().split("\\s+");

            Coord c = new Coord();

            c.a = Integer.parseInt(numstrs[1]);
            c.b = Integer.parseInt(numstrs[2]);
            c.c = Integer.parseInt(numstrs[3]);
            c.d = Integer.parseInt(numstrs[4]);
            c.e = Integer.parseInt(numstrs[5]);
            c.f = Integer.parseInt(numstrs[6]);

            coords.add(c);
        }

        System.out.println("total lines read: "+coords.size());

        int threadsCount = 4;
        List<Thread> ths = new ArrayList<Thread>();

        int blockSize = coords.size()/threadsCount+1;
        for( int i=0; i<threadsCount; ++i  ) {
            CoordsThread ct = new CoordsThread(i*blockSize, (i+1)*blockSize, coords, out);
            ct.setName("Block"+i);
            ths.add(ct);
        }

        for (Thread th : ths) {
            th.start();
        }

        for (Thread th : ths) {
            th.join();
        }

        out.flush();
        out.close();
    }
}

当整个文件都在内存中时,您可以使用双重循环来遍历它们并输出。 - Thomas Jungblut
Yamburg,你是在说可以将所有60000行坐标保存为数组中的元素,然后我应该将一个数组条目分成另一个数组,以便我可以在每个线路中对每个元素进行计算吗? - evanlivingston
2
也可以尝试用 x * x 替换 Math.pow(x, 2) - starblue
我已经更新了代码以展示我的新版本,但仍不满意。 - evanlivingston
你可以通过计算dist(a,b)而不是计算dist(b,a)来立即将时间减少一半。问题是如何以简单的方式保留dist(a,b),以便您可以编写代码... 嗯... - Vladimir Dyuzhev
显示剩余2条评论

1

你正在进行大量重复的IO操作,这是非常昂贵的,比你所做的任何计算都要昂贵得多。

此外,你的问题领域非常适合使用Map/Reduce场景,这不仅易于多线程处理,而且你还应该能够将计算分布到多台机器上。


1

您正在过多地读取文件1.txt。请读取一次,将其存储在类型为int[][]的数组中。

此外,请尝试增加BufferedWriter实例的大小。

同时,让Scanner实例使用适当的字符集在BufferedInputstream上工作。


这个文件只包含数字。字符集是一个高级魔法概念,适用于正在学习数组阶段的人。 - Vladimir Dyuzhev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接