# CPLEX Python API performance overhead?

UPDATE 1: I just found another instance of the same problem where the Python API actually beats the command line (18 288.19 ticks vs. 76 710.09). I naively assumed that both environments share the same default settings, but perhaps it's not the case. Obviously, I should have made more tests and research before jumping the gun!

UPDATE 2: At the request of @BjarniMax, I have launched the CPLEX interpreter on my original instance in single-thread mode. Result: 87 241.71 ticks (vs. 46 513.04 with the Python API in single-thread mode). So, the discrepancy is not (entirely) due to the threads.

UPDATE 3: At the request of @Tobias Achterberg and @jfpuget, I provide my "LP" file, which actually seems to be a command file.

UPDATE 4: Following the mention by @Tobias Achterberg of a "bug" in the Python API parameters' management, I've carried out some more experiments on various instances. Here is how I "reproduce" the CPLEX command line environment in Python:

problem = cplex.Cplex()
problem.set_results_stream("cplex_python.log")
problem.solve()


This indeed changes something for the Python solver, which then behaves exactly as if I would read the data from an SAV file (as first suggested by @Paul Rubin):

problem = cplex.Cplex()
problem.solve()


Unfortunately, the discrepancy still exists, like in this instance, where, on the contrary, the command line solver needs at least 20 more times than the Python one. May be there's still some differences between the two environments? Since I was not able to find the equivalent of "display settings all" in the Python API, I came up with the following equivalent script:

#!arch -i386 python2.7

import cplex

def dumpParameters(prefix,obj):
if hasattr(obj,"get"):
result.append(prefix + "\t" + repr(obj.get()))
else:
for (name,method) in obj.__dict__.iteritems():
if not name.startswith("_"):
dumpParameters(prefix + " " + name,method)

result = []
problem = cplex.Cplex()
dumpParameters("",problem.parameters)
print "\n".join(sorted(result))


The other differences are:

1. simplex perturbation indicator = 0 (exists in Python only)
2. read API encoding = UTF8 (exists in Python only)
3. network display, network iterations, network netfind, network pricing, network tolerances feasibility, network tolerances optimality (exist in CPLEX command line only)

I don't know if the remaining culprits are there. In any case, it's now perfectly clear that using the Python API has no significative intrinsic cost. So, in this sense, my original question is answered. However, it's a little disturbing that I cannot make my results reproducible by the other members of my team (who don't use Python).

# Original question

When I run CPLEX 12.5.0.0 from the command line:

cplex -f my_instance.lp


an optimal integer solution is found in 19056.99 ticks.

But through the Python API, on the very same instance:

import cplex
problem = cplex.Cplex("my_instance.lp")
problem.solve()


the required time now amounts to 97407.10 ticks (more than 5 times slower).

In both cases, the mode is parallel, deterministic, up to 2 threads. Wondering if this poor performance was due to some Python thread overhead, I tried:

problem = cplex.Cplex("my_instance.lp")
problem.solve()


Required 46513.04 ticks (i.e., using one core was two times faster than using two!).

Being new to CPLEX and LP in general, I find these results quite confusing. Is there a way to improve the Python API performance, or should I switch to some more mature API (i.e., Java or C++)?

# Complete log for the 2-threads' resolutions from the command line

Tried aggregator 3 times.
MIP Presolve eliminated 2648 rows and 612 columns.
MIP Presolve modified 62 coefficients.
Aggregator did 13 substitutions.
Reduced MIP has 4229 rows, 1078 columns, and 13150 nonzeros.
Reduced MIP has 1071 binaries, 0 generals, 0 SOSs, and 0 indicators.
Presolve time = 0.05 sec. (18.80 ticks)
Probing fixed 24 vars, tightened 0 bounds.
Probing time = 0.05 sec. (18.12 ticks)
Tried aggregator 1 time.
MIP Presolve eliminated 87 rows and 26 columns.
MIP Presolve modified 153 coefficients.
Reduced MIP has 4142 rows, 1052 columns, and 12916 nonzeros.
Reduced MIP has 1045 binaries, 7 generals, 0 SOSs, and 0 indicators.
Presolve time = 0.03 sec. (11.67 ticks)
Probing time = 0.00 sec. (1.06 ticks)
Clique table members: 4199.
MIP emphasis: balance optimality and feasibility.
MIP search method: dynamic search.
Parallel mode: deterministic, using up to 2 threads.
Root relaxation solution time = 0.19 sec. (91.47 ticks)

Nodes                                         Cuts/
Node  Left     Objective  IInf  Best Integer    Best Bound    ItCnt     Gap

0     0        2.0649   248                      2.0649      915
0     0        2.1300   292                    Cuts: 11     1117
0     0        3.0000   287                    Cuts: 58     1520
0     0        3.0000   268                    Cuts: 55     1669
0     0        3.0000   272                    Cuts: 53     1849
0     0        3.0000   280                    Cuts: 27     2093
0     2        3.0000   279                      3.0000     2155
Elapsed time = 5.54 sec. (2445.18 ticks, tree = 0.01 MB, solutions = 0)
16    16        4.0000   220                      3.0000     5332
25    21        3.0000   421                      3.0000     8584
50    40        4.0000   241                      3.0000    11678
63    49        3.0000   238                      3.0000    13458
78    62        3.0000   239                      3.0000    16544
88    68        3.0000   185                      3.0000    19482
105    83        3.0000   240                      3.0000    22900
113    91        3.1605   358                      3.0000    25822
119    97        3.1906   473                      3.0000    29995
210   188        5.0000   162                      3.0000    46344
Elapsed time = 14.65 sec. (6056.73 ticks, tree = 1.67 MB, solutions = 0)
*   232   196      integral     0        6.0000        3.0000    49431   50.00%
283   233        4.0000   208        6.0000        3.0000    59799   50.00%
421   357        5.0000   173        6.0000        3.0000    72690   50.00%
*   451+  357                            5.0000        3.0000    74233   40.00%
546   279        3.0488   324        5.0000        3.0000    84557   40.00%
*   593+  298                            4.0000        3.0000    91681   25.00%
622    38        3.0000   225        4.0000        3.0000    96092   25.00%
723    31        cutoff              4.0000        3.0000   109728   25.00%
827    27        cutoff              4.0000        3.0000   121652   25.00%
941    17        3.0000   217        4.0000        3.0000   133992   25.00%
1076    24        cutoff              4.0000        3.0000   148970   25.00%
1189    15        3.0000   225        4.0000        3.0000   162854   25.00%
Elapsed time = 32.43 sec. (14836.30 ticks, tree = 0.16 MB, solutions = 3)
1269    13        3.0000   229        4.0000        3.0000   172439   25.00%
1369    15        cutoff              4.0000        3.0000   186650   25.00%
1482    19        3.0000   227        4.0000        3.0000   200118   25.00%
1587     8        3.0000   222        4.0000        3.0000   211275   25.00%

GUB cover cuts applied:  1
Clique cuts applied:  3
Cover cuts applied:  2
Implied bound cuts applied:  38
Zero-half cuts applied:  7
Gomory fractional cuts applied:  2

Root node processing (before b&c):
Real time             =    5.27 sec. (2345.14 ticks)
Real time             =   35.15 sec. (16626.69 ticks)
Sync time (average)   =    0.00 sec.
Wait time (average)   =    0.00 sec.
------------
Total (root+branch&cut) =   40.41 sec. (18971.82 ticks)

Solution pool: 3 solutions saved.

MIP - Integer optimal solution:  Objective =  4.0000000000e+00
Solution time =   40.65 sec.  Iterations = 214773  Nodes = 1652
Deterministic time = 19056.99 ticks  (468.84 ticks/sec)


# Partial log for the 2-threads' resolutions from the Python API

... The beginning is 100% identical up to the node 50, then:

     65    51        4.0000   208                      3.0000    14169
79    63        3.0000   219                      3.0000    17500
100    78        3.0000   202                      3.0000    21773
106    84        3.0000   232                      3.0000    24866
123    99        4.0000   225                      3.0000    32635
136   106        3.1358   348                      3.0000    36276
*   173+  141                            6.0000        3.0000    42524   50.00%
201   166        4.0000   207        6.0000        3.0000    46618   50.00%
Elapsed time = 15.90 sec. (6469.36 ticks, tree = 3.21 MB, solutions = 0)
308   249        5.0000   253        6.0000        3.0000    59008   50.00%


... 100 lines left. Please tell me if you need them too.

Elapsed time = 228.81 sec. (97141.89 ticks, tree = 160.66 MB, solutions = 4)
Nodefile size = 30.63 MB (28.33 MB after compression)

Clique cuts applied:  33
Cover cuts applied:  1
Implied bound cuts applied:  4
Zero-half cuts applied:  10
Gomory fractional cuts applied:  4

Root node processing (before b&c):
Real time             =    6.42 sec. (2345.36 ticks)
Real time             =  222.28 sec. (95061.73 ticks)
Sync time (average)   =    0.01 sec.
Wait time (average)   =    0.00 sec.
------------
Total (root+branch&cut) =  228.70 sec. (97407.10 ticks)


The log ends here, and I haven't found yet how to display the number of branch & bound nodes.

aristide
6125
accept rate: 0%

2

Since the number of cuts is so different between the two runs, maybe the whole behavior (e.g. tree search) was changed. I could imagine this being the result of different random seeds. Could you also show the number of branch & bound nodes?

(09 Mar '13, 04:04)

@aristide: After your updated runs, with the Threads parameter set to 1, was the solution path, between CPLEX standalone and CPLEX Python API, still the same during Presolve, Probing, and the root node (applying cuts)?

(09 Mar '13, 11:42)

@BjarniMax Yes. The first difference occurs 4 "lines" after a first "Best Integer" has been found (node 339).

(09 Mar '13, 11:51)

 4 First of all note that cplex -f my_instance.lp  is not correct syntax, and you should have obtained lots of error messages. I guess what you did was more like cplex -c "read my_instance.lp" "opt"  What you observe is strange. Using the interactive should indeed yield identical behavior as with using Python. Could you please send me your "my_instance.lp" file by email (achterberg (at) de (dot) ibm (dot) com) so that I can investigate? Thanks! Tobias Achterberg answered 15 Mar '13, 10:18 Tobias Achte... 81●1 accept rate: 0% @achterberg Thank you for your interest. You may download the LP file at dl.dropbox.com/u/3108405/my_instance.lp PS: I don't know for previous versions, but CPLEX 12.5.0.0 does accept the -f syntax. (15 Mar '13, 11:21) aristide
 1 I'm wondering if the floating-point control word is the same in the two cases. Python uses the default floating-point environment. The command line version of cplex could change the floating point environment to e.g control if denormals are flushed to zero. Even tiny numerical differences can lead to widely different running times for MIP. answered 13 Mar '13, 07:38 Rune Sandvik 101●2 accept rate: 0%
 1 Thanks to all who responded, good anwers! We, at IBM CPLEX team, do have a question to ask in return. What is the content of the myinstance.lp ? We'd be surprised it it was a plain lp file because "cplex -f " expects a command file, not a problem file. Providing a plain lp file as input should trigger many errors. Can we get the content of the myinstance.lp file? answered 15 Mar '13, 11:59 jfpuget 2.5k●3●10 accept rate: 8% @jfpuget Ah! I now understand why @Tobias Achterberg was surprised by my command line. My file is certainly a command file instead of a plain LP file. What should be the correct extension? I've posted a link to my file in my answer to Tobias, but strangely this answer is not shown :-( I update my question with the link. (15 Mar '13, 12:53) aristide
 toggle preview community wiki

By Email:

Markdown Basics

• *italic* or _italic_
• **bold** or __bold__
• image?![alt text](/path/img.jpg "Title")
• numbered list: 1. Foo 2. Bar
• to add a line break simply add two spaces to where you would like the new line to be.
• basic HTML tags are also supported

Tags:

×191
×16
×4