【问题标题】:Is parallel linear search O(1)?并行线性搜索 O(1) 吗?
【发布时间】:2018-06-11 12:29:34
【问题描述】:

假设我们想要在一个包含 n 个元素的数组中搜索一个元素,并且我们可以创建任意数量的线程。让我们为数组的每个元素分配一个单独的线程,并考虑比较操作比创建线程要昂贵得多。说这样的搜索算法是O(1)是否有效?

代码:

import java.util.concurrent.atomic.AtomicBoolean;

public class scratch {
    static int[] a = new int[100];
    static final int n = 3;
    static boolean expensiveCompare(int pos) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e){}
        return a[pos] == n;
    }

    static AtomicBoolean answer = new AtomicBoolean(false);

    public static void main(String... args) {
        long start = System.currentTimeMillis();
        a[2] = 3;
        Thread[] threads = new Thread[100];
        for(int i=0; i < 100; ++i) {
            final int k = i;
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    if(expensiveCompare(k)) {
                        answer.compareAndExchange(false, true);
                    }
                }
            });
            threads[i].start();
        }

        for(int i = 0; i < 100; ++i) {
            try {
                threads[i].join();
            }catch (InterruptedException e) {}
        }

        System.out.println("Elapsed: " + (System.currentTimeMillis() - start));
        System.out.println("Answer:" + answer);
    }
}

打印:“经过:1000”

public class scratch {
    static final int n = 3;
    static int[] a = new int[100];
    static boolean answer = false;

    static boolean expensiveCompare(int pos) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        return a[pos] == n;
    }

    public static void main(String... args) {
        long start = System.currentTimeMillis();
        a[2] = 3;
        for (int i = 0; i < 100; ++i) {
            answer |= expensiveCompare(i);
        }
        System.out.println("Elapsed: " + (System.currentTimeMillis() - start));
        System.out.println("Answer:" + answer);
    }
}

打印:“Elapsed 100000”。

【问题讨论】:

  • Here 是一个有趣的问题。与您的问题没有直接关系,但总体而言是不错的阅读。
  • 说到 linear 搜索有点冒险,因为这意味着对数组进行有序扫描,这是一个无法并行化的内在顺序过程。你应该放弃限定符。

标签: algorithm math big-o


【解决方案1】:

您的代码是 O(1),不是因为线程,而是因为数组大小是恒定的 (100)。如果我们用一个变量替换它,那么这个:

for(int i=0; i < n; ++i) {

已经是 O(n)。不管你在循环中做什么(只要你不改变in),循环头本身已经执行了O(n)的指令。

更一般地说,你甚至不能在 O(1) 时间内启动 n 个线程,所以启动 n 线程不能给你 O(1) 的运行时间(当然这是假设所有线程都是甚至并行运行,即假装你有无限数量的核心)。

考虑到比较操作比创建线程要昂贵得多

每个操作的成本有多高并不重要。这些只是常量,big-O 并不关心常量。

【讨论】:

  • >你甚至不能在少于 O(n) 的时间内启动 n 个线程。这就是为什么我注意到比较比启动线程更昂贵。
  • @POrekhov 做一些廉价的事情n 次仍然是O(n)。说它便宜只是意味着我们谈论的是较小的常数,但常数在 big-O 中并不重要。
  • 但是随着元素数量的增加,Search/(Create n threads) 的比值会增加,(Create n threads) 操作变得微不足道。
  • @POrekhov 您可以在 O(log n) 时间内启动 n 个线程,只需启动两个线程,每个线程启动两个线程等。我也相信还有像 cuda 这样的框架,您可以在其中并行启动 n 个操作只需一个电话。所以,这一切都取决于计算模型。
  • @POrekhov 如果您可以在 O(1) 时间内启动任意多个 个线程,这将是 O(1)。请注意,如果有一个系统可以并行运行任意数量的线程(通过按需创建新内核或类似的东西 - 一种自我复制的 CPU),那么在物理上不可能在 O (1次。在理论系统之外,不可能在 O(1) 时间内创建任意数量的东西。
【解决方案2】:

让我们假设元素 N 的数量是无限的,并且可以并行运行任意数量的线程。

我们甚至假设所有线程都已经启动并运行,以避免 O(N) 成本。然后可以在 O(1) 时间内完成比较,每个线程一个。但这还不是全部:您还想知道是否找到了密钥,以及由哪个线程找到。

这个结果集合不能在 O(1) 中执行,因为没有处理器指令处理无限数量的参数。由于该数字是有界的,即使忽略共享内存的争用,您能做的最好的事情是以树状方式合并 N 个结果(N => N/k => N/k² => N/k³... => 1),经过O(Log N)个阶段后得到单个结果(待处理的位数以几何方式减少)。

所以复杂度充其量是 O(Log N)。 [具有讽刺意味的是,如果数据经过排序,您无法击败十亿个线程。]


这是一个一般规则:无论计算能力如何,所有输入数据都有助于解决方案的问题可以在 O(1) 时间内解决。

这深入到物理学:您无法构建一个与 2 输入逻辑门一样快的 N 输入逻辑门。

【讨论】:

    【解决方案3】:

    请注意,即使您有一百万个线程,也必须将每个线程分配给特定的 CPU 才能执行。

    在现实生活中,CPU 或计算节点的数量是有限的。如果您有 k 个 CPU,则类似于 O(n/k),因为您一次进行 k 个比较。

    现在,如果 k 远小于 n(这对于 PC 来说很典型),那么 O(n/k) ~ O(n)。就像你的 CPU 有 4 个内核(并且它没有增长),k = 4,如果 n = 100000 可能,那么 O(n/k) 仍然是 O(n)。

    如果你的数组非常小,并且 k 接近于 n(对于某个常数 C,n/k

    【讨论】:

    • “如果你有无限的计算能力,每个算法都变成 O(1)”:这是完全错误的。一个简单的操作,比如对 N 位进行 XOR,至少需要 Log(N) 个阶段,即使有无穷多个处理器。
    • 确实如此。将“every”改为“this”以避免引起争议:)
    • 恐怕你没有完全理解。即使是“这个”算法也无法在 O(1) 时间内运行。
    • @YvesDaoust 你是什么意思?就像一些假设的大整数 x 你在其中做类似 x ^ something 的事情?你能告诉我你反对什么异或吗?
    • @Water:确切的操作无关紧要(我选择了 single bits 的 XORing,因为这是您能想到的最简单的操作)。您不能在 O(1) 时间内对 N 位进行异或运算,您甚至可以更少地添加 N 个整数,也不能取最小的也不是...
    【解决方案4】:

    这个问题涉及大 O 表示法的一个更微妙的方面:成本模型

    当我们考虑某个算法需要多少个“步骤”来完成一项任务时,我们需要决定在一个步骤中我们可以做什么。大多数时候,人们会假设一个成本模型,其中任何基本指令(例如比较、基本算术运算或函数调用)都需要 1 步。这是一个有用的简化,即使在真实的计算机中,这些操作可能需要非常不同的时间来执行,具体取决于模型未考虑的因素(例如,我们是否在寄存器、缓存、主存储器中添加两个数字,在磁盘上?)因为这样做可以让我们考虑程序如何扩展到更大的输入,而不必担心太多细节。

    但是,在某些应用程序中,不同的成本模型是合适的:例如,在编写网络软件时,您可能希望将网络传输计为一个步骤,而不管算法如何,在单个节点上发生的任何计算都是免费的。同样,根据需要从主内存读取的次数来分析旨在与内存局部性良好配合的算法可能很有用,而忽略了他们对所读取内容所做的任何工作的成本。这些方法都不是“正确”的方法——它们都在测量一个抽象的、虚构的数字——但它们可能对不同的事情有用。

    所以直接回答你的问题:这个算法是 O(1) 还是 O(n) 取决于成本模型。如果您决定启动线程是空闲的并且在任意数量的线程上的一个操作都算作一个步骤,那么是的,它是 O(1)。如果您决定启动一个线程需要一个工作单元,或者单个线程上的操作算作一个工作单元,那么它就是 O(n)。哪种成本模型适合您的目的取决于您要回答的问题。

    (不那么腼腆一点,我想说第一个成本模型对我来说似乎并不是那么有用,因为我不知道在任何情况下我想假设我可以拥有尽可能多的CPU 是我需要的,或者分担工作的成本可以忽略不计,但如果我正在分析一种算法来处理具有大型 MapReduce 集群的数据或其他有意义的东西。)

    【讨论】:

    • 怎么可能是O(1)?
    • "这个算法是 O(1) 还是 O(n) 取决于成本模型。如果您决定启动线程是空闲的,并且对任意数量的线程的一个操作算作一个步骤,那么是的,它是 O(1)。”
    • 你能告诉我一个可以实现的概念算法吗? (任何类型的描述)
    • 我认为我解释得不够清楚——这与算法本身无关,而与我们计算其成本的方式有关。换句话说,我们算什么大O of?例如,大多数关于 big-O 的说明从不谈论运行多个线程的成本是多少——我们是对任何线程上的每个步骤收取 1 个工作单元,还是在所有线程上同时执行一个步骤收取 1 个总工作单元?任何一种选择都是站得住脚的,这取决于你想弄清楚你的程序。
    • 当然是采用第二个选项。你是如何实现 O(1) 的?
    猜你喜欢
    • 1970-01-01
    • 2010-11-06
    • 1970-01-01
    • 2013-02-04
    • 2018-12-04
    • 1970-01-01
    • 2016-10-07
    • 1970-01-01
    相关资源
    最近更新 更多