如何在 Java 中并行运行某些东西?

2023-12-05

我正在尝试打印一个范围内的所有可能的组合。例如,如果我的lowerBound是 3,我的max是 5,我想要以下组合:(5,4 - 5,3 - 4,3)。我已经用这个实现了helper()下面找到的函数。

当然,如果我的最大值很大,则需要很多组合,这将需要很长时间。这就是为什么我试图实施一个ForkJoinPool,以便任务并行运行。为此我创建了一个新的ForkJoinPool。然后我循环遍历 r 的所有可能值(其中 r 是组合中的数字数量,在上面的示例中r=3)。对于 r 的每个值,我创建一个新的HelperCalculator,这延伸了RecursiveTask<Void>。在那里我递归地调用helper()功能。每次我调用这个我都会创建一个新的HelperCalculator我用.fork()关于这一点。

问题如下。它没有正确生成所有可能的组合。它实际上根本不生成任何组合。我尝试过添加calculator.join() after calculator.fork(),但这会无限地持续下去,直到我得到OutOfMemory error.

显然,我对 ForkJoinPool 有一些误解,但在尝试了几天之后,我再也看不到什么了。

我的主要功能:

            ForkJoinPool pool = (ForkJoinPool) Executors.newWorkStealingPool();
            for (int r = 1; r < 25; r++) {
                int lowerBound = 7;
                int[] data = new int[r];
                int max = 25;
                calculator = new HelperCalculator(data, 0, max, 0, s, n, lowerBound);
                pool.execute(calculator);
                calculator.join();
            }
            pool.shutdown();

助手计算器类:

    protected Void compute() {
        helper(data, end, start, index, s, lowerBound);
        return null;
    }

    //Generate all possible combinations
    public void helper(int[] data , int end, int start, int index,int s, int lowerBound) {
        //If the array is filled, print it
        if (index == data.length) {
                System.out.println(Arrays.toString(data));
        } else if (start >= end) {
            data[index] = start;
            if(data[0] >= lowerBound) {
                HelperCalculator calculator = new HelperCalculator(data,end, start-1, index+1, s, n, lowerBound);
                calculator.fork();
                calculators.add(calculator);
                HelperCalculator calculator2 = new HelperCalculator(data, end, start-1, index, s, n, lowerBound);
                calculator2.fork();
                calculators.add(calculator2);
            }
        }

我怎样做每一个HelperCalculator并行运行,以便有 23 个使用 ForkJoinPool 同时运行?或者我应该使用不同的解决方案?

我试过打电话join() and isDone() on the calculators列表,但它不会等待它正确完成,程序就会退出。

由于有人不明白算法,这里是:

    public static void main(String[] args) {
            for(int r = 3; r > 0; r--) {
                int[] data = new int[r];
                helper(data, 0, 2, 0);
            }
    }

    public static void helper(int[] data , int end, int start, int index) {
        if (index == data.length) {
            System.out.println(Arrays.toString(data));
        } else if (start >= end) {
            data[index] = start;
                helper(data, end, start - 1, index + 1);
                helper(data, end, start - 1, index);
            }
        }
    }

其输出是:

[2, 1, 0]
[2, 1]
[2, 0]
[1, 0]
[2]
[1]
[0]

您正在分叉的某些任务尝试使用相同的数组来评估不同的组合。您可以通过为每个任务创建一个不同的数组或将并行性限制为那些已经拥有自己的数组的任务(即具有不同长度的任务)来解决该问题。

但还有另一种可能;根本不要使用数组。您可以将组合存储到int值,作为每个int值是位的组合。这不仅节省了大量内存,而且您还可以通过仅增加值来轻松迭代所有可能的组合,因为迭代所有int数字还会迭代所有可能的位组合。我们唯一需要实现的是为特定的字符串生成正确的字符串int通过根据位的位置将位解释为数字来获取值。

对于第一次尝试,我们可以采取简单的方法并使用已经存在的类:

public static void main(String[] args) {
    long t0 = System.nanoTime();
    combinations(10, 25);
    long t1 = System.nanoTime();
    System.out.println((t1 - t0)/1_000_000+" ms");
    System.out.flush();
}
static void combinations(int start, int end) {
    for(int i = 1, stop = (1 << (end - start)) - 1; i <= stop; i++) {
        System.out.println(
            BitSet.valueOf(new long[]{i}).stream()
                  .mapToObj(b -> String.valueOf(b + start))
                  .collect(Collectors.joining(", ", "[", "]"))
        );
    }
}

该方法使用独占结束,因此对于您的示例,您必须像这样调用它combinations(0, 3)它会打印

[0]
[1]
[0, 1]
[2]
[0, 2]
[1, 2]
[0, 1, 2]
3 ms

of course, timing may vary

For the combinations(10, 25)上面的例子,它打印所有组合,然后是3477 ms在我的机器上。这听起来像是一个优化的机会,但我们应该首先考虑哪些操作会产生哪些成本。

在这里,组合的迭代已被简化为一个微不足道的操作。创建字符串的成本要高一个数量级。但这与实际打印相比仍然不算什么,实际打印包括将数据传输到操作系统,并且根据系统的不同,实际渲染可能会增加我们的时间。由于这是在持有锁的情况下完成的PrintStream,同时尝试打印的所有线程都将被阻止,从而使其成为不可并行的操作。

让我们通过创建一个新的来确定成本的比例PrintStream,禁用换行符上的自动刷新并使用一个非常大的缓冲区,能够保存整个输出:

public static void main(String[] args) {
    System.setOut(new PrintStream(
        new BufferedOutputStream(new FileOutputStream(FileDescriptor.out),1<<20),false));
    long t0 = System.nanoTime();
    combinations(10, 25);
    long t1 = System.nanoTime();
    System.out.flush();
    long t2 = System.nanoTime();
    System.out.println((t1 - t0)/1_000_000+" ms");
    System.out.println((t2 - t0)/1_000_000+" ms");
    System.out.flush();
}
static void combinations(int start, int end) {
    for(int i = 1, stop = (1 << (end - start)) - 1; i <= stop; i++) {
        System.out.println(
            BitSet.valueOf(new long[]{i}).stream()
                  .mapToObj(b -> String.valueOf(b + start))
                  .collect(Collectors.joining(", ", "[", "]"))
        );
    }
}

在我的机器上,它按以下顺序打印一些内容

93 ms
3340 ms

显示代码在不可并行打印上花费了超过三秒的时间,而在计算上只花费了约 100 毫秒。为了完整起见,以下代码降低了一个级别String一代:

static void combinations(int start, int end) {
    for(int i = 1, stop = (1 << (end - start)) - 1; i <= stop; i++) {
        System.out.println(bits(i, start));
    }
}
static String bits(int bits, int offset) {
    StringBuilder sb = new StringBuilder().append('[');
    for(;;) {
        int bit = Integer.lowestOneBit(bits), num = Integer.numberOfTrailingZeros(bit);
        sb.append(num + offset);
        bits -= bit;
        if(bits == 0) break;
        sb.append(", ");
    }
    return sb.append(']').toString();
}

这使我的机器上的计算时间减半,同时对总时间没有明显影响,现在这应该不足为奇。


但出于教育目的,忽略潜在加速的缺乏,让我们讨论如何并行化此操作。

顺序代码确实已经将任务转化为一种形式,该形式可以归结为从起始值到最终值的迭代。现在,我们将这段代码重写为ForkJoinTask(或合适的子类)表示具有开始值和结束值的迭代。然后,我们通过在中间分割范围来添加将此操作分割为两个的功能,这样我们就可以在范围的每一半上迭代两个任务。可以重复此操作,直到我们决定有足够的潜在并行作业并在本地执行当前迭代。在本地处理之后,我们必须等待我们拆分的任何任务的完成,以确保根任务的完成意味着所有子任务的完成。

public class Combinations extends RecursiveAction {
    public static void main(String[] args) {
        System.setOut(new PrintStream(new BufferedOutputStream(
            new FileOutputStream(FileDescriptor.out),1<<20),false));
        ForkJoinPool pool = (ForkJoinPool) Executors.newWorkStealingPool();
        long t0 = System.nanoTime();
        Combinations job = Combinations.get(10, 25);
        pool.execute(job);
        job.join();
        long t1 = System.nanoTime();
        System.out.flush();
        long t2 = System.nanoTime();
        System.out.println((t1 - t0)/1_000_000+" ms");
        System.out.println((t2 - t0)/1_000_000+" ms");
        System.out.flush();
    }

    public static Combinations get(int min, int max) {
        return new Combinations(min, 1, (1 << (max - min)) - 1);
    }

    final int offset, from;
    int to;

    private Combinations(int offset, int from, int to) {
        this.offset = offset;
        this.from = from;
        this.to = to;
    }

    @Override
    protected void compute() {
        ArrayDeque<Combinations> spawned = new ArrayDeque<>();
        while(getSurplusQueuedTaskCount() < 2) {
            int middle = (from + to) >>> 1;
            if(middle == from) break;
            Combinations forked = new Combinations(offset, middle, to);
            forked.fork();
            spawned.addLast(forked);
            to = middle - 1;
        }
        performLocal();
        for(;;) {
            Combinations forked = spawned.pollLast();
            if(forked == null) break;
            if(forked.tryUnfork()) forked.performLocal(); else forked.join();
        }
    }

    private void performLocal() {
        for(int i = from, stop = to; i <= stop; i++) {
            System.out.println(bits(i, offset));
        }
    }

    static String bits(int bits, int offset) {
        StringBuilder sb = new StringBuilder().append('[');
        for(;;) {
            int bit=Integer.lowestOneBit(bits), num=Integer.numberOfTrailingZeros(bit);
            sb.append(num + offset);
            bits -= bit;
            if(bits == 0) break;
            sb.append(", ");
        }
        return sb.append(']').toString();
    }
}

The getSurplusQueuedTaskCount()为我们提供了有关工作线程饱和度的提示,换句话说,分叉更多作业是否可能有益。将返回的数字与阈值进行比较,该阈值通常是一个小数字,作业越异构,因此预期的工作负载,阈值就应该越高,以在作业比其他作业更早完成时允许更多的工作窃取。在我们的例子中,工作量预计会非常平衡。

分裂的方式有两种。示例通常创建两个或多个分叉子任务,然后将它们连接起来。这可能会导致大量任务只是等待其他任务。另一种方法是分叉子任务并更改当前任务,以代表另一个任务。这里,分叉任务代表[middle, to]范围,而当前任务被修改为代表[from, middle] range.

分叉足够多的任务后,剩余范围将在当前线程中本地处理。然后,该任务将等待所有分叉的子任务,并进行一项优化:try to unfork子任务,如果还没有其他工作线程窃取它们,则在本地处理它们。

这工作顺利,但不幸的是,正如预期的那样,它并没有加速操作,因为最昂贵的部分是打印。


1 使用int表示所有组合将支持的范围长度减少到 31,但请记住,这样的范围长度意味着2³¹ - 1组合,需要迭代很多。如果这仍然是一个限制,您可以更改代码以使用long反而。当时支持的范围长度为 63,换句话说2⁶³ - 1组合,足以让计算机一直忙碌到宇宙的尽头。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 Java 中并行运行某些东西? 的相关文章

随机推荐

  • next.js npm 模块注释给出错误

    这是我的错误 10 03 56 049 components navbar js 10 03 56 049 13 33 Warning img elements must have an alt prop either with meani
  • C++ 类何时在堆栈上实例化?

    我想澄清一下在堆栈上实例化类时会发生什么 当 C 类在堆上实例化时 MyClass myclass new MyClass 创建 MyClass 类型的指针 并通过 new MyClass 在同一行实例化该类 像这样拉伸它 MyClass
  • 使用 GDK 在 Google Glass 上接收 UDP 数据

    对于从 Google Glass 上运行的应用程序接收 UDP 数据有什么建议吗 我需要与现有系统集成 该系统向本地子网进行 UDP 广播 Glass 将位于同一子网中 并且在 Glass 上运行的应用程序只需侦听端口上的 UDP 数据包
  • 无法找到 EL RI 表达式工厂

    我在我的应用程序中遇到以下异常 com sun faces config ConfigurationException 看来JSP版本的 容器早于 2 1 并且无法 定位 EL RI 表达式 工厂 com sun el E xpressio
  • vue如何从子组件访问v-model

    我的孩子组件是这样的
  • PHP 一秒发送多个号码短信请求

    我正在尝试使用 API 发送短信 它几乎每秒发送一条短信 但我想在一秒钟内使用 PHP 中的多线程 pthreads 发送多条短信 这怎么可能 或者我怎样才能至少从我的一端异步发送多个短信请求到API服务器 Threads Class cl
  • Matlab 中 IFFT 的缩放问题

    我正在 Matlab 中研究 IFFT 将其应用于高斯 根据维基百科表 傅里叶变换对将是 F w sqrt pi a exp w 2 4a 频率 以及 f t exp at 2 及时 我修改了代码上一个问题加上 Cris Luengo 执行
  • PHP 在字符串中动态引用变量

    我的表单中有多个 PHP 变量 number1 number2 number3 and so on 我想在循环内部动态引用它们以从中检索信息 但不确定如何动态引用静态变量 前任 for i 1 i lt 10 i The first num
  • 如何在 MS Access 2007 或 MS SQL Server 2005 中通过 SQL 将字段转换为行

    我有一个旧的 MS Access 2007 表 其中包含 52 个字段 一年中的每周 1 个字段 代表历史销售数据 加上实际年份的一个字段 我想将此数据库转换为更传统的时间 值列表 有谁知道如何在不编写带有 52 个以上显式参数的查询的情况
  • Net::SSH 与非 UNIX/Linux 主机?

    我正在尝试使用 Net SSH 库来登录和管理支持 ssh 的主机 TL1 是一种电信设备 我似乎能够成功登录 但是当我尝试 ssh exec 某些内容时 它会中止并表示无法执行命令 这是我的简单代码 require net ssh Net
  • .NET Core API 中的自定义授权过滤器

    我想在使用我的核心 api 访问任何数据之前对用户进行授权 所以我尝试使用 JWT 身份验证 我在使用 api 登录用户时成功生成了令牌 并将该令牌保存在会话中的客户端 现在每当用户想要使用 api 访问任何数据时 我都会将该令牌在标头中发
  • 如何通过 HTTP 从 Internet 检索文件?

    我想从 Internet 下载文件 乍一看 InternetReadFile 似乎是一个很好且简单的解决方案 事实上 好得令人难以置信 事实上 经过一番挖掘 我开始发现它实际上存在很多问题 人们在使用这段代码时抱怨各种各样的问题 出现问题的
  • 如何在打印函数中定义变量?

    我是这个领域的新手 我正在尝试解决一个问题 不太确定实际上是否可能 我想在显示器上打印一些信息以及用户的一些输入 以下工作正常 gt gt gt print Hello input tellmeyourname tellmeyourname
  • 无法找到要实例化的界面控制器类“InterfaceController”

    每次运行项目并尝试导航到另一个屏幕时 我都会收到此错误 Unable to find interface controller class HelpInterfaceController to instantiate 我正在正确使用我所知道
  • 设备向上/向下和侧向倾斜会触发方向通知

    我有一个针对 iOS7 的应用程序构建 其中 UIViewController 应该支持横向左右和纵向 纵向上下颠倒 其他 ViewController 应该仅支持横向左右方向 我已使用通知来通知方向更改并相应地刷新子视图 我还在检查 UI
  • 如何使用XSLT仅获取某些行和某些列?

    如何使用 XSLT 转换此 XML 文件
  • 从 iPhone 应用程序拨打电话 [重复]

    这个问题在这里已经有答案了 可能的重复 从我的应用程序中拨打 iPhone 电话 我想通过 iPhone 应用程序拨打给定号码 您能建议任何最好的教程来解释它或告诉我这个过程吗 你可以试试 NSURL phoneNumberURL NSUR
  • 单击展开包含详细信息和摘要标签

    我正在使用单击展开折叠使用
  • 将文件内容读入ArrayList

    在之前的项目中 我需要将文件内容读取到数组中 现在我必须做同样的事情 只是我必须将内容读入 ArrayList 我遇到的几个问题是 如何逐步浏览 ArrayList 并分别添加每个项目 如果文件包含超过 10 个输入 则必须退出 我已经尝试
  • 如何在 Java 中并行运行某些东西?

    我正在尝试打印一个范围内的所有可能的组合 例如 如果我的lowerBound是 3 我的max是 5 我想要以下组合 5 4 5 3 4 3 我已经用这个实现了helper 下面找到的函数 当然 如果我的最大值很大 则需要很多组合 这将需要