Skip to main content

Memory leak with multi threaded application

Answered

Comments

14 comments

  • Riley Clement
    Gurobi Staff Gurobi Staff

    Hi Nate,

    Thank you for the reproducible example, I was able to whittle it down to the following:

    #include "gurobi_c++.h"

    int main() 
    {
      GRBEnv   env   = GRBEnv();
      GRBModel model = GRBModel(env);
      GRBVar* x = model.addVars(2, GRB_BINARY);
      return 0;
    }

    It looks like the issue is produced by not freeing up the memory allocated to the x variables, and can be resolved by adding

    delete[] x;

    - Riley

    0
  • Nathan Adelgren
    Conversationalist
    First Question

    Hi Riley,

    Thanks very much for your help on this!

    Unfortunately, it didn't solve my problem -- at least not entirely.

    Certainly, I was too hasty in making my MWE and definitely should have added "delete[] x", as you recommend.

    When I saw your comment last night, I checked my original program to see if I had forgotten a similar statement, but I hadn't. Interestingly, I ran the program using valgrind and received no memory leaks. I didn't change anything in the code; the only difference was that I was using my personal laptop, rather than my work machine. When I arrived at work this morning, I added the delete statement that you suggested to my MWE, and I'm still getting the memory leak. I tried placing the delete statement before the calls to std::thread and after the join statements, but I get the same result either way.

    I'm not sure if you can help me further, since I know it will now be difficult to reproduce the issue, but any advice you can give would be much appreciated. I'm happy to provide any additional information you think may be helpful. For now, I can tell you that both machines are using Gurobi 11.0.0 build v11.0.0rc2. Additionally, my personal machine -- the one that does NOT produce a leak -- is running Linux Mint 20 with kernel 5.4.0-169-generic and the valgrind version is 3.15.0, while my work machine -- the one that produces the leak -- is running Ubuntu 22.04.3 LTS with kernel 6.2.0-39-generic and the valgrind version is 3.18.1.

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    Hi Nate,

    Can you see if you get a memory leak with the following modification to the main function?

    int main() 
    {
      GRBEnv   env   = GRBEnv();
      GRBModel model = GRBModel(env);
      model.set(GRB_IntParam_Threads, 1);
     GRBVar x = model.addVar(0.0, 1.0, 0.0, GRB_BINARY, "x");
    model.addConstr(x == 0.0, "c1");
    GRBLinExpr obj = x;
      model.setObjective(obj);

      std::thread first  (foo, model);   
      std::thread second (bar, model);  

      first.join();
      second.join();

      std::cout << "foo and bar completed.\n";

      return 0;
    }

    - Riley

    0
  • Nathan Adelgren
    Conversationalist
    First Question

    Hi Riley,

    Unfortunately, I still get the leak even with the modified main function.

    I did also notice that my personal machine has an Intel CPU whereas my work machine has an AMD. Not sure if it helps ...

    Thanks again!

    -Nate

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    Thanks Nate,

    Can you post the Valgrind output from this latest run?

    We may need to swing this over to our dev team, but I will see if my teammates have any ideas.

    - Riley

    0
  • Nathan Adelgren
    Conversationalist
    First Question

    Sure Riley,

    Here it is:

    ==348169== 
    ==348169== HEAP SUMMARY:
    ==348169==     in use at exit: 1,785 bytes in 55 blocks
    ==348169==   total heap usage: 985 allocs, 930 frees, 2,194,508 bytes allocated
    ==348169== 
    ==348169== 1,785 (40 direct, 1,745 indirect) bytes in 1 blocks are definitely lost in loss record 10 of 10
    ==348169==    at 0x484DA83: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==348169==    by 0x861CA35: ???
    ==348169==    by 0x8616C1B: ???
    ==348169==    by 0x8604FBA: ???
    ==348169==    by 0x860C068: ???
    ==348169==    by 0x79B6D9E: getpwuid_r@@GLIBC_2.2.5 (getXXbyYY_r.c:273)
    ==348169==    by 0x5270108: GRBgetusername (in /home/adelgren/Downloads/gurobi11.0.0_linux64/gurobi1100/linux64/lib/libgurobi.so.11.0.0)
    ==348169==    by 0x526B885: PRIVATE00000000009da1bf (in /home/adelgren/Downloads/gurobi11.0.0_linux64/gurobi1100/linux64/lib/libgurobi.so.11.0.0)
    ==348169==    by 0x526A6A0: PRIVATE00000000009d60cc (in /home/adelgren/Downloads/gurobi11.0.0_linux64/gurobi1100/linux64/lib/libgurobi.so.11.0.0)
    ==348169==    by 0x52695E8: PRIVATE00000000009d75e8 (in /home/adelgren/Downloads/gurobi11.0.0_linux64/gurobi1100/linux64/lib/libgurobi.so.11.0.0)
    ==348169==    by 0x522A51F: GRBloadenvadv (in /home/adelgren/Downloads/gurobi11.0.0_linux64/gurobi1100/linux64/lib/libgurobi.so.11.0.0)
    ==348169==    by 0x11C6C1: GRBEnv::GRBEnv(bool) (in /home/adelgren/Insync/Google_Drive/USNA/Scholarship/Lakmali/QMOSCP/Cpp_implementation/Thread_Example/solver)
    ==348169== 
    ==348169== LEAK SUMMARY:
    ==348169==    definitely lost: 40 bytes in 1 blocks
    ==348169==    indirectly lost: 1,745 bytes in 54 blocks
    ==348169==      possibly lost: 0 bytes in 0 blocks
    ==348169==    still reachable: 0 bytes in 0 blocks
    ==348169==         suppressed: 0 bytes in 0 blocks
    ==348169== 
    ==348169== For lists of detected and suppressed errors, rerun with: -s
    ==348169== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

    Thanks!

    0
  • David Torres Sanchez
    Gurobi Staff Gurobi Staff

    Hi Nathan,

    What C++ std are you using to compile this? I have tried reproducing this on a similar machine with no success (tried 11, 17, and 2a).
    Also, what type of license are you using?

    Additionally, I have lost the output that you get (I accidentally deleted it while formatting your post).

    Cheers, 
    David

    0
  • Nathan Adelgren
    Conversationalist
    First Question

    Hi David,

    I've tried compiling with -std=c++17 and -std=c++11, same result either way. I've also tried with -O3, -O2, -O and with and without -g. All produce the same result. I've tried with the g++ compiler (version 11.4.0) and with g++-9 (which is version 9.5.0, and happens to be the version installed on the machine that does not produce the issue), both give the same. I have not tried downgrading to valgrind version 3.15, though.

    I'm using named-user academic licenses on both machines.

    Just so you have everything, the file I'm currently running has the following contents:

    #include <iostream>
    #include <thread>
    #include <mutex>

    #include "gurobi_c++.h"

    static std::mutex mtx;
     
    void foo(GRBModel model) 
    {
      GRBEnv env2     = GRBEnv();
      mtx.lock();
      GRBModel model2 = GRBModel(model, env2);
      mtx.unlock();
      model2.set(GRB_IntParam_Threads, 1);
      model2.optimize();
    }

    void bar(GRBModel model)
    {
      GRBEnv env2     = GRBEnv();
      mtx.lock();
      GRBModel model2 = GRBModel(model, env2);
      mtx.unlock();
      model2.set(GRB_IntParam_Threads, 1);
      model2.optimize();
    }

    int main() 
    {
      GRBEnv   env   = GRBEnv();
      GRBModel model = GRBModel(env);
      model.set(GRB_IntParam_Threads, 1);
      model.set(GRB_IntParam_OutputFlag, 0);
      GRBVar x = model.addVar(0.0, 1.0, 0.0, GRB_BINARY, "x");
      model.addConstr(x == 0.0, "c1");
      GRBLinExpr obj = x;
      model.setObjective(obj);

      std::thread first  (foo, model);   
      std::thread second (bar, model);  

      first.join();
      second.join();
      

      std::cout << "foo and bar completed.\n";

      return 0;
    }

    and running 

    valgrind --leak-check=full ./solver

    from my terminal produces:

    ==358636== Memcheck, a memory error detector
    ==358636== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==358636== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
    ==358636== Command: ./solver
    ==358636== 
    Set parameter Username
    Academic license - for non-commercial use only - expires 2025-01-10
    Set parameter Threads to value 1
    Set parameter Username
    Set parameter Username
    Academic license - for non-commercial use only - expires 2025-01-10
    Set parameter Threads to value 1
    Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (linux64 - "Ubuntu 22.04.3 LTS")

    CPU model: AMD Ryzen 7 PRO 5850U with Radeon Graphics, instruction set [SSE2|AVX|AVX2]
    Thread count: 8 physical cores, 16 logical processors, using up to 1 threads

    Optimize a model with 0 rows, 0 columns and 0 nonzeros
    Model fingerprint: 0xf9715da1
    Coefficient statistics:
      Matrix range     [0e+00, 0e+00]
      Objective range  [0e+00, 0e+00]
      Bounds range     [0e+00, 0e+00]
      RHS range        [0e+00, 0e+00]
    Presolve time: 0.06s
    Presolve: All rows and columns removed
    Iteration    Objective       Primal Inf.    Dual Inf.      Time
           0    0.0000000e+00   0.000000e+00   0.000000e+00      0s

    Solved in 0 iterations and 0.11 seconds (0.00 work units)
    Optimal objective  0.000000000e+00
    Academic license - for non-commercial use only - expires 2025-01-10
    Set parameter Threads to value 1
    Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (linux64 - "Ubuntu 22.04.3 LTS")

    CPU model: AMD Ryzen 7 PRO 5850U with Radeon Graphics, instruction set [SSE2|AVX|AVX2]
    Thread count: 8 physical cores, 16 logical processors, using up to 1 threads

    Optimize a model with 0 rows, 0 columns and 0 nonzeros
    Model fingerprint: 0xf9715da1
    Coefficient statistics:
      Matrix range     [0e+00, 0e+00]
      Objective range  [0e+00, 0e+00]
      Bounds range     [0e+00, 0e+00]
      RHS range        [0e+00, 0e+00]
    Presolve time: 0.00s
    Presolve: All rows and columns removed
    Iteration    Objective       Primal Inf.    Dual Inf.      Time
           0    0.0000000e+00   0.000000e+00   0.000000e+00      0s

    Solved in 0 iterations and 0.00 seconds (0.00 work units)
    Optimal objective  0.000000000e+00
    foo and bar completed.
    ==358636== 
    ==358636== HEAP SUMMARY:
    ==358636==     in use at exit: 1,785 bytes in 55 blocks
    ==358636==   total heap usage: 985 allocs, 930 frees, 2,194,508 bytes allocated
    ==358636== 
    ==358636== 1,785 (40 direct, 1,745 indirect) bytes in 1 blocks are definitely lost in loss record 10 of 10
    ==358636==    at 0x484DA83: calloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==358636==    by 0x861CA35: ???
    ==358636==    by 0x8616C1B: ???
    ==358636==    by 0x8604FBA: ???
    ==358636==    by 0x860C068: ???
    ==358636==    by 0x79B6D9E: getpwuid_r@@GLIBC_2.2.5 (getXXbyYY_r.c:273)
    ==358636==    by 0x5270108: GRBgetusername (in /home/adelgren/Downloads/gurobi11.0.0_linux64/gurobi1100/linux64/lib/libgurobi.so.11.0.0)
    ==358636==    by 0x526B885: PRIVATE00000000009da1bf (in /home/adelgren/Downloads/gurobi11.0.0_linux64/gurobi1100/linux64/lib/libgurobi.so.11.0.0)
    ==358636==    by 0x526A6A0: PRIVATE00000000009d60cc (in /home/adelgren/Downloads/gurobi11.0.0_linux64/gurobi1100/linux64/lib/libgurobi.so.11.0.0)
    ==358636==    by 0x52695E8: PRIVATE00000000009d75e8 (in /home/adelgren/Downloads/gurobi11.0.0_linux64/gurobi1100/linux64/lib/libgurobi.so.11.0.0)
    ==358636==    by 0x522A51F: GRBloadenvadv (in /home/adelgren/Downloads/gurobi11.0.0_linux64/gurobi1100/linux64/lib/libgurobi.so.11.0.0)
    ==358636==    by 0x11C6D1: GRBEnv::GRBEnv(bool) (in /home/adelgren/Insync/Google_Drive/USNA/Scholarship/Lakmali/QMOSCP/Cpp_implementation/Thread_Example/solver)
    ==358636== 
    ==358636== LEAK SUMMARY:
    ==358636==    definitely lost: 40 bytes in 1 blocks
    ==358636==    indirectly lost: 1,745 bytes in 54 blocks
    ==358636==      possibly lost: 0 bytes in 0 blocks
    ==358636==    still reachable: 0 bytes in 0 blocks
    ==358636==         suppressed: 0 bytes in 0 blocks
    ==358636== 
    ==358636== For lists of detected and suppressed errors, rerun with: -s
    ==358636== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

    Interestingly, on my other machine, the number of allocs and frees is also quite different, but the total number of bytes used is very similar -- off by about 2000.

    I can post the contents of my makefile, if it helps.

    Thanks!

    -Nate

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    Unfortunately I've also not been able to reproduce on a VM with

    Ubuntu 22.04.3 LTS
    kernel 6.2.0-39-generic
    valgrind 3.18.1
    g++ 11.4

    and different combinations of std and optimizations.

    ==958== HEAP SUMMARY:
    ==958==     in use at exit: 0 bytes in 0 blocks
    ==958==   total heap usage: 3,613 allocs, 3,613 frees, 2,318,444 bytes allocated

     

    0
  • Nathan Adelgren
    Conversationalist
    First Question

    Thanks Riley,

    It's very strange! I guess I don't have anything additional to add at this point. If I'm able to reproduce the issue on any other machine, or find something that seems to resolve it, I'll post an additional comment.

    I appreciate your help!

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    No worries Nate.  If you have admin rights over that machine it could be an interesting experiment to install multipass and spin up VMs on that machine with different OS versions and kernels and see if the problem persists.

    - Riley

    0
  • David Torres Sanchez
    Gurobi Staff Gurobi Staff

    I created a ticket to track this internally.

    Cheers, 
    David

    0
  • Nathan Adelgren
    Conversationalist
    First Question

    Thanks Riley and David. I'm happy to help in whatever way I can moving forward.

    -Nate

    0
  • David Torres Sanchez
    Gurobi Staff Gurobi Staff

    We investigated this and found out that it was due to several things and not related to multi-threading.

    The leak occurs when checking the license (this is done when starting an environment) using a named-user license.
    On Linux, we use a function from glibc to check the user name which is known to be leaky. Particularly, as also seen in the valgrind output in this post, this is glibc's \(\texttt{getpwuid_r}\). This has been discussed e.g.:

    Seeing this leak depends on the glibc version of the machine, the OS, Valgrind options (see below), and the Gurobi version (v11.0.0 updated to a minimum glibc version of 2.17, see Detailed Release Notes).

    It is safe to ignore this leak (people on Stackoverflow also say this).

    Valgrind even has the option:

    --run-libc-freeres=no|yes free up glibc memory at exit on Linux? [yes]

    Additionally, many other license types (e.g. WLS) do not use this leaky function.

    Cheers, 
    David

    0

Please sign in to leave a comment.