Skip to content

Slow computation with multiple threads for emd2 #698

Closed

Description

@dc250601

Setting numThreads to more than 1 for emd2 calculation slows down the process even more. The entire calculations process is extremely slow how do I speed it up ?

Activity

  1. rflamary commented on Nov 27, 2024

    @rflamary
    Collaborator

    could you please provide us with a code example? Our experience wa sthat openmp lead to a small computational gain

  2. axiomcura commented on Mar 21, 2026

    @axiomcura

    Hello @rflamary - I realize this thread is quite old, but I’ve been encountering the same issue and wanted to share some observations.

    When increasing the number of threads, the computation actually becomes significantly slower. To investigate this, I created a small benchmarking repository for reference. I’ll include one representative figure here, and the full set of results is available in the repo.

    Image The figure above shows emd2() Runtime and Parallelization Efficiency Across Sample Sizes. (There's a typo it should be samples not dims)

    These two plots benchmark the emd2() function's performance as a function of thread count, averaged across feature dimensions, for datasets ranging from N=500 to N=15,000 samples.

    Left: Wall-clock time increases with thread count for all sample sizes - larger datasets (N=15,000) are hit hardest, exceeding 24 seconds at 48 threads. The red dashed line marks the single-thread optimum.

    Right: Speedup relative to 1 thread drops below 1.0 almost immediately for all sample sizes, with smaller datasets (N=500–2,000) degrading fastest. Most configurations fall below the 2× slower threshold (0.5×, orange dashed line) by 48 threads.

    Key takeaway: emd2() does not benefit from multithreading regardless of sample size - single-threaded execution is consistently optimal.

    Again, Please feel free to point out anything that might be incorrect in my setup or interpretation - I’d really appreciate the feedback!

    Here's the env list:

    Package         Version
    --------------- -----------
    contourpy       1.3.3
    cycler          0.12.1
    fonttools       4.62.1
    kiwisolver      1.5.0
    matplotlib      3.10.8
    numpy           2.4.3
    packaging       26.0
    pandas          3.0.1
    pillow          12.1.1
    pot             0.9.6.post1
    pyparsing       3.3.2
    python-dateutil 2.9.0.post0
    scipy           1.17.1
    six             1.17.0
    
    

    My current intuition is that when specifying a single thread, additional parallelism may still be occurring under the hood (e.g., through dependencies like NumPy or SciPy).

    One way to probe this is by using Linux’s perf tool to monitor CPU usage when explicitly setting numThreads=1. For example:

    perf stat python3 -c "
    import numpy as np
    import ot
    
    n = 2000
    a, b = np.ones((n,)) / n, np.ones((n,)) / n
    x, y = np.random.randn(n, 2), np.random.randn(n, 2)
    M = ot.dist(x, y)
    
    # EMD2 calculation with explicit single thread
    ot.emd2(a, b, M, numThreads=1)
    "

    Here are the results I got:

    Performance counter stats for 'python3 -c 
    import numpy as np
    import ot
    
    n = 2000
    a, b = np.ones((n,)) / n, np.ones((n,)) / n
    x, y = np.random.randn(n, 2), np.random.randn(n, 2)
    M = ot.dist(x, y)
    
    # EMD2 Calculation with explicit single thread
    ot.emd2(a, b, M, numThreads=1)
    ':
    
        11,168,281,535      task-clock:u                     #   10.649 CPUs utilized             
                     0      context-switches:u               #    0.000 /sec                      
                     0      cpu-migrations:u                 #    0.000 /sec                      
                38,086      page-faults:u                    #    3.410 K/sec                     
        18,049,038,096      instructions:u                   #    0.50  insn per cycle            
                                                      #    0.51  stalled cycles per insn   
        36,266,464,691      cycles:u                         #    3.247 GHz                       
           210,325,849      stalled-cycles-frontend:u        #    0.58% frontend cycles idle      
         9,281,072,572      stalled-cycles-backend:u         #   25.59% backend cycles idle       
         2,567,343,375      branches:u                       #  229.878 M/sec                     
            14,711,723      branch-misses:u                  #    0.57% of all branches           
    
           1.048794365 seconds time elapsed
    
          10.928324000 seconds user
           0.254841000 seconds sys
    
    
    

    It uses 10 CPUs when specifying 1 thread.

    Please let me know if there's anything that I can do to make this clearer!

  3. rflamary commented on Jul 7, 2026

    @rflamary
    Collaborator

    Thanks so much for these reports, we had an old openmp impleetation that was indeed not really faster and very hard to maintain. We decided to remove it in #820 and have greatly acelerated the single thread C++ solver but it indeed remains on 1 CPU only for the moment. Network simplex is notoriously hard to paralellize and we understand that the previous attempt was a failure. We might try again in the future or if someone want to contribute but from release 0.9.7 the multi-threadding in emd solver is deprecated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions