合并大型文件与内存限制并行(Linux)

时间:2018-04-01 17:34:14

标签: c linux sorting

我需要使用M线程对大小为t的大型二进制文件进行排序。文件中的记录大小相同。该任务明确指出我可以分配的内存量为m,并且远小于M。此外,硬盘保证至少有2 * M个可用空间。这需要合并排序ofc,但事实证明它并不那么明显。我在这里看到三种不同的方法:

A 即可。将文件inputtemp1temp2映射到内存中。执行合并排序input -> temp1 -> temp2 -> temp1 ...,直到其中一个临时排序。线程只争用选择下一部分工作,没有读/写争用。

即可。每个fopen 3个文件t次,每个线程获得3个FILE个指针,每个文件一个。他们再次争论下一部分工作,读写应该并行工作。

C 即可。每次打开3个文件,将它们保存在互斥锁下,所有线程并行工作,但要抓取更多工作或读取或写入,它们会锁定相应的互斥锁。

备注:

在现实生活中,我肯定会选择 A 。但是它不能破坏缓冲区有限的全部目的吗? (换句话说,不是作弊吗?)。通过这种方法,我甚至可以在没有额外缓冲的情况下对整个文件进行基本排序此解决方案也是特定于Linux的,我认为Linux是通过对话暗示的,但在任务描述中没有明确说明。

关于 B ,我认为它适用于Linux但不可移植,请参阅上面的Linux注释。

关于 C ,它是可移植的,但我不确定如何对其进行优化(例如,8个具有足够小m的线程只会在队列中等待轮到他们,然后读/写微小数据的一部分,然后立即对它进行排序并再次相互碰撞.IMO不太可能比1个线程更快地工作。)

问题:

  1. 哪种解决方案更适合任务?
  2. 哪种解决方案在现实生活中是更好的设计(假设是Linux)?
  3. B 有效吗?换句话说,是多次打开文件并且并行写入(到它的不同部分)合法?
  4. 任何替代方法?

3 个答案:

答案 0 :(得分:2)

你的问题有很多方面,所以我会尝试分解一下,同时尝试回答几乎所有的问题:

  • 您可以在存储设备上获得一个大文件,该文件可能在上运行,即您可以同时加载和存储多个条目。如果从存储中访问单个条目,则必须处理相当大的访问延迟,您只能通过同时加载多个元素来尝试隐藏,从而在所有元素加载时间内分摊延迟。

  • 与存储(特别是随机访问)相比,您的主内存非常快,因此您希望尽可能多地在主内存中保存数据,并且只在存储上读取和写入顺序块。这也是为什么 A 没有真正作弊的原因,因为如果您尝试使用存储进行随机访问,那么使用主存储器的速度会慢一些。

结合这些结果,您可以得到以下方法,基本上是 A ,但有一些通常在外部算法中使用的工程细节。

  • 仅使用一个专用线程来读取和写入存储。 这样,每个文件只需要一个文件描述符,理论上甚至可以在很短的时间内收集和重新排序来自所有线程的读写请求,以获得几乎顺序的访问模式。此外,您的线程可以将写入请求排队并继续下一个块,而无需等待IO完成。

  • t 块(从input)加载到最大大小的主内存中,以便您可以在每个上并行运行 mergesort 块。对块进行排序后,将其作为temp1写入存储 重复此操作,直到文件中的所有块都已排序。

  • 现在对已排序的块执行所谓的多路合并: 每个线程将一定数量的 k 连续块从temp1加载到内存中,并使用优先级队列或锦标赛树合并它们,以找到要插入到结果块中的下一个最小值。只要您的块已满,就会将其写入temp2的存储空间以释放下一个块的内存。完成此步骤后,从概念上交换temp1temp2

  • 你仍然需要做几个合并步骤,但这个数字比 log k 要小一些,而你可能在 A中的常规双向合并。在前几个合并步骤之后,您的块可能太大而无法放入主内存中,因此您将它们拆分为较小的块,并且从第一个小块开始,仅在所有先前元素已经被取出时才获取下一个块合并。在这里,你甚至可以进行一些预取,因为块访问的顺序是由块最小值预先确定的,但这可能超出了这个问题的范围。 请注意, k 的值通常仅受可用内存的限制。

  • 最后,您到达 t 需要合并在一起的巨大块。我真的不知道是否有一个很好的并行方法,可能需要按顺序合并它们,所以再次使用上面的t-way合并来生成一个单独的排序文件。 / p>

答案 1 :(得分:0)

Gnu sort是一种用于文本文件的多线程合并排序,但它的基本功能可以在这里使用。定义一个" chunk"作为可以在大小 m 的内存中排序的记录数。

排序阶段:对于每个" chunk"记录,阅读" chunk"记录,在" chunk"上使用多线程排序然后写一个" chunk"记录到临时文件,最后是天花板( M / m )临时文件。 Gnu sort对记录的指针数组进行排序,部分原因是记录是可变长度的。对于固定大小的记录,在我的测试中,由于缓存问题,直接对记录进行排序比对记录指针数组进行排序更快(导致缓存不友好的随机访问记录),除非记录大小更大比128到256字节之间。

合并阶段:对临时文件执行单线程k-way合并(例如优先级队列),直到生成单个文件。多线程在这里没有帮助,因为它假设k-way合并阶段是I / O绑定而不是cpu绑定。对于Gnu排序,k的默认值为16(它对临时文件进行16路合并)。

为了避免超过2 x M 空间,一旦读取文件就需要删除。

答案 2 :(得分:0)

如果您的文件大于RAM大小,那么这就是解决方案。 https://stackoverflow.com/a/49839773/1647320

如果您的文件大小是RAM大小的70-80%,那么以下是解决方案。它是内存中的并行合并排序。

enter image description here

根据您的系统更改此行。 fpath是您的一个大输入文件。 shared路径是存储执行日志的地方。fdir是存储和合并中间文件的位置。根据您的机器更改这些路径。

public static final String fdir = "/tmp/";
    public static final String shared = "/exports/home/schatterjee/cs553-pa2a/";
    public static final String fPath = "/input/data-20GB.in";
    public static final String opLog = shared+"Mysort20GB.log";

然后运行以下程序。将在fdir路径中使用名称op2GB创建最终排序文件。最后一行Runtime.getRuntime()。exec(“valsort”+ fdir +“op”+(treeHeight * 100)+ 1 +“>”+ opLog);检查输出是否已排序。如果您的计算机上没有安装valsort,或者未使用gensort(http://www.ordinal.com/gensort.html)生成输入文件,请删除此行。

另外,不要忘记更改int totalLines = 20000000;到文件中的总行数。和线程数(int threadCount = 8)应始终为2的幂。

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.LinkedList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.stream.Stream;


class SplitJob extends Thread {
    LinkedList<String> chunkName;
    int startLine, endLine;

    SplitJob(LinkedList<String> chunkName, int startLine, int endLine) {
        this.chunkName = chunkName;
        this.startLine = startLine;
        this.endLine = endLine;
    }

    public void run() {
        try {
            int totalLines = endLine + 1 - startLine;
            Stream<String> chunks =
                    Files.lines(Paths.get(Mysort2GB.fPath))
                            .skip(startLine - 1)
                            .limit(totalLines)
                            .sorted(Comparator.naturalOrder());
            chunks.forEach(line -> {
                chunkName.add(line);
            });
            System.out.println(" Done Writing " + Thread.currentThread().getName());

        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

class MergeJob extends Thread {
    int list1, list2, oplist;
    MergeJob(int list1, int list2, int oplist) {
        this.list1 = list1;
        this.list2 = list2;
        this.oplist = oplist;
    }

    public void run() {
        try {
            System.out.println(list1 + " Started Merging " + list2 );
            LinkedList<String> merged = new LinkedList<>();
            LinkedList<String> ilist1 = Mysort2GB.sortedChunks.get(list1);
            LinkedList<String> ilist2 = Mysort2GB.sortedChunks.get(list2);

            //Merge 2 files based on which string is greater.
            while (ilist1.size() != 0 || ilist2.size() != 0) {
                if (ilist1.size() == 0 ||
                        (ilist2.size() != 0 && ilist1.get(0).compareTo(ilist2.get(0)) > 0)) {
                    merged.add(ilist2.remove(0));
                } else {
                    merged.add(ilist1.remove(0));
                }
            }
            System.out.println(list1 + " Done Merging " + list2 );
            Mysort2GB.sortedChunks.remove(list1);
            Mysort2GB.sortedChunks.remove(list2);
            Mysort2GB.sortedChunks.put(oplist, merged);
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

public class Mysort2GB {
    //public static final String fdir = "/Users/diesel/Desktop/";
    public static final String fdir = "/tmp/";
    public static final String shared = "/exports/home/schatterjee/cs553-pa2a/";
    public static final String fPath = "/input/data-2GB.in";
    public static HashMap<Integer, LinkedList<String>> sortedChunks = new HashMap();
    public static final String opfile = fdir+"op2GB";
    public static final String opLog = shared + "mysort2GB.log";


    public static void main(String[] args) throws Exception{
        long startTime = System.nanoTime();
        int threadCount = 8; // Number of threads
        int totalLines = 20000000;
        int linesPerFile = totalLines / threadCount;
        LinkedList<Thread> activeThreads = new LinkedList<Thread>();


        for (int i = 1; i <= threadCount; i++) {
            int startLine = i == 1 ? i : (i - 1) * linesPerFile + 1;
            int endLine = i * linesPerFile;
            LinkedList<String> thisChunk = new LinkedList<>();
            SplitJob mapThreads = new SplitJob(thisChunk, startLine, endLine);
            sortedChunks.put(i,thisChunk);
            activeThreads.add(mapThreads);
            mapThreads.start();
        }
        activeThreads.stream().forEach(t -> {
            try {
                t.join();
            } catch (Exception e) {
            }
        });

        int treeHeight = (int) (Math.log(threadCount) / Math.log(2));

        for (int i = 0; i < treeHeight; i++) {
            LinkedList<Thread> actvThreads = new LinkedList<Thread>();
            for (int j = 1, itr = 1; j <= threadCount / (i + 1); j += 2, itr++) {
                int offset = i * 100;
                int list1 = j + offset;
                int list2 = (j + 1) + offset;
                int opList = itr + ((i + 1) * 100);
                MergeJob reduceThreads =
                        new MergeJob(list1,list2,opList);
                actvThreads.add(reduceThreads);
                reduceThreads.start();
            }
            actvThreads.stream().forEach(t -> {
                try {
                    t.join();
                } catch (Exception e) {
                }
            });
        }
        BufferedWriter writer = Files.newBufferedWriter(Paths.get(opfile));
        sortedChunks.get(treeHeight*100+1).forEach(line -> {
            try {
                writer.write(line+"\r\n");
            }catch (Exception e){

            }
        });
        writer.close();
        long endTime = System.nanoTime();
        double timeTaken = (endTime - startTime)/1e9;
        System.out.println(timeTaken);
        BufferedWriter logFile = new BufferedWriter(new FileWriter(opLog, true));
        logFile.write("Time Taken in seconds:" + timeTaken);
        Runtime.getRuntime().exec("valsort  " + opfile + " > " + opLog);
        logFile.close();
    }
}


  [1]: https://i.stack.imgur.com/5feNb.png