多线程总是比单线程产生更好的性能吗?

7fyelxc5  于 2021-07-06  发布在  Java
关注(0)|答案(7)|浏览(845)

我知道答案是否定的,下面是一个例子,为什么在java中单线程比多线程更快。
因此,当在线程中处理任务很简单时,创建线程的成本将比分发任务产生更多的开销。这是一种单线程比多线程更快的情况。

问题

单线程比多线程速度快的情况更多吗?
我们应该在什么时候决定放弃多线程而只使用一个线程来实现我们的目标?
尽管这个问题被标记为java,但也欢迎讨论超越java的问题。如果我们能在答案中用一个小例子来解释,那就太好了。

p8h8hvxi

p8h8hvxi1#

这里可能发生两种情况:
单核cpu上的多线程:
1.1何时使用:当需要并行性的任务被io绑定时,多线程有帮助。线程在等待io时放弃执行,操作系统将时间片分配给其他等待的线程。顺序执行没有行为-多线程将提高性能。
1.2何时不使用:当任务不受io限制并且仅仅是计算某个内容时,您可能不想使用多线程,因为线程创建和上下文切换将抵消增益(如果有的话)。-多线程的影响最小。
多核cpu中的多线程:多核可以运行与cpu中的核数相同的线程数。这肯定会提高性能。但是运行比可用内核更多的线程将再次引入线程上下文切换问题。-多线程肯定会产生影响。
注意:在系统中添加/引入线程的数量也有限制。更多的上下文切换将抵消整体增益,应用程序速度会减慢。

vddsk6oq

vddsk6oq2#

这是一个关于线程及其与实际工作的联系的非常好的问题,这意味着可用的物理cpu及其内核和超线程。
如果您的cpu有多个可用的内核,多个线程可能允许您并行执行任务。因此,在一个理想的世界中,例如计算一些素数,如果你的cpu有4个内核可用,并且你的算法是并行的,那么使用4个线程可能会快4倍。
如果在内核可用的情况下启动更多线程,那么操作系统的线程管理将在线程切换上花费越来越多的时间,这样您使用cpu的效率就会变得越来越差。
如果编译器、cpu缓存和/或运行时意识到您运行多个线程,访问内存中的同一数据区域,将以不同的优化模式运行:只要编译/运行时确保只有一个线程访问数据,is可以避免太频繁地将数据写入扩展ram,并且可以有效地使用cpu的一级缓存。否则:is必须激活信号量,并且更频繁地将缓存数据从一级/二级缓存刷新到ram。
因此,我从高度并行的多线程处理中学到的经验是:
如果可能的话,使用单线程、无共享的进程来提高效率
如果需要线程,则尽可能地解耦共享数据访问
如果可能,不要尝试分配比可用内核更多的已加载工作线程
这里有一个小程序(javafx)可以玩。信息技术:
分配一个大小为100.000.000的字节数组,用随机字节填充
提供一种方法,计算此数组中设置的位数
该方法允许每“n”个字节对位进行计数
count(0,1)将对所有字节位进行计数
count(0,4)将对0',4',8'字节位进行计数,允许并行交错计数
使用macpro(4核)会导致:
运行一个线程,count(0,1)需要1326ms来计算所有399993625位
并行运行count(0,2)和count(1,2)两个线程需要920ms
运行四个线程,需要618ms
运行8个线程,需要631ms




改变计数方式,例如增加一个公共共享整数(atomicinteger或synchronized),将极大地改变许多线程的性能。

public class MulithreadingEffects extends Application {
    static class ParallelProgressBar extends ProgressBar {
        AtomicInteger myDoneCount = new AtomicInteger();
        int           myTotalCount;
        Timeline      myWhatcher = new Timeline(new KeyFrame(Duration.millis(10), e -> update()));
        BooleanProperty running = new SimpleBooleanProperty(false);

        public void update() {
            setProgress(1.0*myDoneCount.get()/myTotalCount);
            if (myDoneCount.get() >= myTotalCount) {
                myWhatcher.stop();
                myTotalCount = 0;
                running.set(false);
            }
        }

        public boolean isRunning() { return myTotalCount > 0; }
        public BooleanProperty runningProperty() { return running; }

        public void start(int totalCount) {
            myDoneCount.set(0);
            myTotalCount = totalCount;
            setProgress(0.0);
            myWhatcher.setCycleCount(Timeline.INDEFINITE);
            myWhatcher.play();
            running.set(true);
        }

        public void add(int n) {
            myDoneCount.addAndGet(n);
        }
    }

    int mySize = 100000000;
    byte[] inData = new byte[mySize];
    ParallelProgressBar globalProgressBar = new ParallelProgressBar();
    BooleanProperty iamReady = new SimpleBooleanProperty(false);
    AtomicInteger myCounter = new AtomicInteger(0);

    void count(int start, int step) {
        new Thread(""+start){
            public void run() {
                int count = 0;
                int loops = 0;
                for (int i = start; i < mySize; i+=step) {
                    for (int m = 0x80; m > 0; m >>=1) {
                        if ((inData[i] & m) > 0) count++;
                    }
                    if (loops++ > 99) {
                        globalProgressBar.add(loops);
                        loops = 0;
                    }
                }
                myCounter.addAndGet(count);
                globalProgressBar.add(loops);
            }
        }.start();
    }

    void pcount(Label result, int n) {
        result.setText("("+n+")");
        globalProgressBar.start(mySize);
        long start = System.currentTimeMillis();
        myCounter.set(0);
        globalProgressBar.runningProperty().addListener((p,o,v) -> {
            if (!v) {
                long ms = System.currentTimeMillis()-start;
                result.setText(""+ms+" ms ("+myCounter.get()+")");
            }
        });
        for (int t = 0; t < n; t++) count(t, n);
    }

    void testParallel(VBox box) {
        HBox hbox = new HBox();

        Label result = new Label("-");
        for (int i : new int[]{1, 2, 4, 8}) {
            Button run = new Button(""+i);
            run.setOnAction( e -> {
                if (globalProgressBar.isRunning()) return;
                pcount(result, i);
            });
            hbox.getChildren().add(run);
        }

        hbox.getChildren().addAll(result);
        box.getChildren().addAll(globalProgressBar, hbox);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {        
        primaryStage.setTitle("ProgressBar's");

        globalProgressBar.start(mySize);
        new Thread("Prepare"){
            public void run() {
                iamReady.set(false);
                Random random = new Random();
                random.setSeed(4711);
                for (int i = 0; i < mySize; i++) {
                    inData[i] = (byte)random.nextInt(256);
                    globalProgressBar.add(1);
                }
                iamReady.set(true);
            }
        }.start();

        VBox box = new VBox();
        Scene scene = new Scene(box,400,80,Color.WHITE);
        primaryStage.setScene(scene);

        testParallel(box);
        GUIHelper.allowImageDrag(box);

        primaryStage.show();   
    }

    public static void main(String[] args) { launch(args); }
}
qlckcl4x

qlckcl4x3#

单线程比多线程速度快的情况更多吗?
因此,在gui应用程序中,您将受益于多线程。在最基本的层面上,您将更新前端以及前端所呈现的内容。如果你在运行一些基本的东西,比如helloworld,那么就像你展示的那样,它会有更多的开销。
这个问题很广泛。。。你把单元测试算作应用程序吗?如果是这样的话,那么可能会有更多的应用程序使用单线程,因为任何复杂的系统都(希望)至少有一个单元测试。你认为每个helloworld风格的程序都是不同的还是相同的?如果一个应用程序被删除了,它还算数吗?
正如你所看到的,我不能给出一个很好的回答,除非你不得不缩小你的问题的范围以得到一个有意义的答案。尽管如此,这可能是一个我不知道的统计数字。
我们应该在什么时候决定放弃多线程而只使用一个线程来实现我们的目标?
当多线程将执行'更好'的任何指标,你认为是重要的。
你的问题能被分解成可以同时处理的部分吗?不是像把helloworld分成两个线程,其中一个线程等待另一个线程打印那样的人为方式。但是在某种程度上,2+个线程能够比1个线程更有效地完成任务?
即使任务很容易并行化,也不意味着它应该是并行的。我可以用多线程处理一个应用程序,它可以不断地浏览成千上万个新站点来获取我的新闻。对我个人来说,这会很糟糕,因为它会吃掉我的管道,我将无法得到我的fps。对于cnn来说,这可能正是他们想要的,他们将构建一个大型机来容纳它。
你能缩小你的问题范围吗?

30byixjq

30byixjq4#

并不是所有的算法都可以并行处理(严格顺序的算法;其中p=0在阿姆达定律中)或至少不有效(见p-完全)。其他算法更适合并行执行(极端情况称为“令人尴尬的并行”)。
与类似的顺序算法相比,并行算法的简单实现在复杂度或空间方面可能效率较低。如果没有明显的方法来并行化一个算法以获得加速,那么您可能需要选择另一个类似的并行算法来解决相同的问题,但效率可能会有所提高或降低。如果忽略线程/进程的创建和直接的进程间通信开销,那么在使用共享资源时仍然可能存在其他限制因素,如io瓶颈或更高内存消耗导致的分页增加。
我们应该在什么时候决定放弃多线程而只使用一个线程来实现我们的目标?
在决定使用单线程还是多线程时,应该考虑更改实现所需的时间以及开发人员所增加的复杂性。如果使用多线程只获得很小的收益,那么您可能会认为,通常由多线程应用程序引起的维护成本增加不值得加速。

brvekthn

brvekthn5#

开销可能不仅用于创建,而且用于线程间通信。需要注意的另一点是,例如,单个对象上线程的同步可能会导致类似的单线程执行。

vhipe2zx

vhipe2zx6#

线程是指利用空闲资源来处理更多的工作。如果没有空闲资源,多线程就没有任何优势,因此开销实际上会使整个运行时更长。
例如,如果要执行的任务集合是cpu密集型计算。如果您只有一个cpu,多线程可能不会加快这个进程(尽管在测试之前您永远不会知道)。我希望它会稍微慢一点。您正在更改工作的分配方式,但容量没有更改。如果您在一个cpu上有4个任务要做,那么串行地执行它们是非常必要的 1 * 4 . 如果你把它们平行地做,你会发现基本上 4 * 1 ,这是相同的。另外,合并结果和上下文切换的开销。
现在,如果您有多个cpu,那么在多个线程中运行cpu密集型任务将允许您利用未使用的资源,因此每单位时间可以完成更多的任务。
另外,考虑其他资源。如果您有4个查询数据库的任务,那么并行运行这些任务有助于确保数据库有额外的资源来处理所有这些任务。不过,您还添加了更多的工作,这会从数据库服务器中删除资源,所以我可能不会这么做。
现在,假设我们需要对3个外部系统进行web服务调用,并且所有调用都没有相互依赖的输入。将它们与多个线程并行执行意味着我们不必等待一个线程结束,而等待另一个线程开始。这也意味着并行运行它们不会对每个任务产生负面影响。这将是多线程的一个很好的用例。

whhtz7ly

whhtz7ly7#

正如@jim mischel在评论中提到的,您可以使用

阿姆达定律

来计算这个。阿姆达尔定律指出,通过增加处理器来解决任务所获得的加速比是

哪里
n是处理器的数量,并且
p是可以并行执行的代码的分数(0。。1)
现在,如果t是在单个处理器上执行任务所需的时间,o是总的“开销”时间(创建和设置第二个线程、通信等),那么如果
t<t/s(2)+o
或者,在重新排序之后,如果
o/t>p/2
当开销/执行时间之比大于p/2时,单个线程速度更快。

相关问题