Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
ACCU 2015
PROJECT
DATE CONFERENCE
23 APRIL
PREMATURE OPTIMISATION WORKSHOP
ARJAN VAN LEEUWEN
WWW.OPERA.COM
JOIN THE COOL KIDS ON THE INFORMATION SUPERHIGHWAY
ACCU 2015
PROJECT
DATE CONFERENCE
23 APRIL
PREMATURE OPTIMISATION WORKSHOP
ARJAN VAN LEEUWEN
A short conversation
OPTIMISING IS FUN
AND KNOWING HOW TO DO IT CAN BE USEFUL
PREMATURE
OPTIMISATION
IS THE ROOT OF
ALL EVIL
DONALD KNUTH,
“STRUCTURED
PROGRAMMING WITH GOTO
STATEMENTS”
PROGRAMMERS WASTE ENORMOUS AMOUNTS OF
TIME THINKING ABOUT, OR WORRYING ABOUT, THE
SPEED OF NONCRITICAL PARTS OF THEIR
PROG...
IN ESTABLISHED ENGINEERING
DISCIPLINES A 12%
IMPROVEMENT, EASILY OBTAINED,
IS NEVER CONSIDERED MARGINAL
AND I BELIEVE THE ...
SMALL THINGS CAN MAKE A DIFFERENCE
AND ARE WORTH STUDYING
Goals
Find small changes that can
make a difference

Don’t sacrifice elegance for
speed

Give ideas on how to
optimise
In the toolbox
Common sense (doing
nothing is always faster)

Disassembler

Time measurement

Profiling tools
MICRO-OPTIMISATIONS IN C++
C++
Close to the metal

Object model well-defined [Lippman96]

Efficiency has been a major design goal for C++
from the begin...
Branches
Basis of much we do in
imperative languages

Compare and branch
if-else-if
void GetAndProcessResult() {
if (GetResult() == DOWNLOADED)
return ProcessDownloadedFile();
else if (GetResult(...
if-else-if
void GetAndProcessResult() {
const int result = GetResult();
if (result == DOWNLOADED)
return ProcessDownloaded...
if-else-if switch!
void GetAndProcessResult() {
switch (GetResult()) {
case DOWNLOADED:
return ProcessDownloadedFile();
ca...
The joys of switch
Clarifies intention

Clearer warnings / error messages

Always allows compiler to create jump table or d...
Jump table
void GetAndProcessResult() {
switch (GetResult()) {
case DOWNLOADED:
return ProcessDownloadedFile();
case NEEDS...
Jump table
void GetAndProcessResult() {
switch (GetResult()) {
}
}
case 0:
return ProcessDownloadedFile();
case 1:
return ...
Jump table
case 0: return ProcessDownloadedFile();
case 1: return DownloadFile();
case 2: return ReportNotAvailable();
cas...
Jump table
void GetAndProcessResult() {
switch (GetResult()) {
}
}
case 0:
return ProcessDownloadedFile();
case 1:
return ...
Jump table
void GetAndProcessResult() {
switch (GetResult()) {
case 102:
return ProcessDownloadedFile();
case 103:
return ...
Jump table
void GetAndProcessResult() {
switch (GetResult()) {
case 102+0:
return ProcessDownloadedFile();
case 102+1:
ret...
Jump table?
void GetAndProcessResult() {
switch (GetResult()) {
case 1:
return ProcessDownloadedFile();
case 16:
return Do...
Jump table Binary search
void GetAndProcessResult() {
switch (GetResult()) {
case 1:
return ProcessDownloadedFile();
case ...
Predicting branches
Predicting branches is hard

Automated mechanisms (profile-guided
optimisations) can offer big gains at ...
Strings
Most used and mis-used
type in programming

Mutable strings are the root
of all evil
Strings misuse
String is not a basic type

A mutable string is a dynamic array of characters

Almost anything you can do w...
Using std::string
Be careful with modifying operations such as
append()

Avoid creating a string out of many parts, better...
Growing strings
std::string CopyString(
const char* to_copy, size_t length) {
std::string copied;
for (size_t i = 0; i < l...
Growing strings
std::string CopyString(
const char* to_copy, size_t length) {
std::stringstream copied;
for (size_t i = 0;...
Growing strings
std::string CopyString(
const char* to_copy, size_t length) {
std::string copied;
copied.reserve(length);
...
Growing strings
Method Time spent, 3 run average (ms)
std::string::append() 1399
std::stringstream 5102
std::string::appen...
Converting numbers to
strings and vice versa
Can be a major source of slowness

Often more features than needed

Investiga...
Integer-to-string conversion
std::string Convert(int i) {
std::stringstream stream;
stream << i;
return stream.str();
}
Integer-to-string conversion
std::string Convert(int i) {
return std::to_string(i);
}
Integer-to-string conversion
std::string Convert(int i) {
namespace karma = boost::spirit::karma;
std::string converted;
s...
Integer-to-string conversion
Method Time spent, 3 run average (ms)
std::stringstream 2959
std::to_string 1012
boost::spiri...
String-to-integer
conversion
int Convert(const std::string& str) {
return std::stoi(str);
}
String-to-integer
conversion
int Convert(const std::string& str) {
namespace qi = boost::spirit::qi;
int converted;
qi::pa...
String-to-integer
conversion
Method Time spent, 3 run average (ms)
std::stoi 3920
boost::spirit::qi 1276
Function calls
Function calls have
overhead

Lookup in virtual function
table

Setting up stack, restoring
stack
Avoiding virtual functions or
virtual function calls
Only declare functions (this includes destructors)
virtual when it’s ...
Avoiding function calls
For small functions called in tight loops, inlining
helps

Allow the compiler to inline functions ...
Tail calls
A tail call happens when a function is the final call
made in another function

Tail calls can be eliminated, so...
Facilitating tail calls
unsigned djb_hash(const char* string) {
int c = *string;
if (!c)
return 5381;
return djb_hash(stri...
Facilitating tail calls
unsigned djb_hash(
const char* string, unsigned seed) {
int c = *string;
if (!c)
return seed;
retu...
Facilitating tail calls
Method Time spent, 3 run average (ms)
Tail call elimination not possible 2274
Tail call eliminatio...
Use lambda functions
C++11 lambdas can always be trivially inlined,
unlike function pointers

Offers an elegant and fast wa...
Use lambda functions
void twice(int& value) {
value *= 2;
}
std::vector<int> EverythingTwice(
const std::vector<int>& orig...
Use lambda functions
std::vector<int> EverythingTwice2(
const std::vector<int>& original) {
std::vector<int> result(origin...
Use lambda functions
Method Time spent, 3 run average (ms)
Function pointer (not inlined) 1684
Lambda function (inlined) 2...
Return-value optimisation
Allows the compiler to avoid copy construction on
temporaries

Executed by compilers when functi...
Move semantics
User defines for movable types how they can be
moved correctly

‘Guaranteed’ way of getting return value
opt...
Move semantics
class Typical {
public:
Typical()
: content_("this is a typical string") {}
Typical(const Typical& other)
:...
Move semantics
class Typical {
public:
TypicalMove ()
: content_("this is a typical string") {}
private:
std::string conte...
Move semantics
std::vector<Typical> CreateTypical() {
std::vector<Typical> new_content;
for (int i = 0; i < 1024; ++i)
new...
Move semantics
Method Time spent, 3 run average (ms)
With copy constructor 2617
Following “Rule of zero” 1002
Data
Make sure that all data you
need in a loop is physically
as close together as possible

Allows CPU to use its cache
e...
Data
int sum() {
std::forward_list<int> data(1024, 5);
int result;
for (int i = 0; i < 1000000; ++i) {
result = std::accum...
Data
int sum() {
std::vector<int> data(1024, 5);
int result;
for (int i = 0; i < 1000000; ++i) {
result = std::accumulate(...
Data
Method Time spent, 3 run average (ms)
std::forward_list 1115
std::vector 61
MICRO-OPTIMISATIONS IN PYTHON
Python
Emphasises readability

Dynamic type system, automatic memory
management

Several projects dedicated to improving
p...
Prefer literals over
“constructors”
def a():
return dict(firstkey=1, secondkey=2)
Prefer literals over
“constructors”
def a():
return dict(firstkey=1, secondkey=2)
def b():
return { 'firstkey': 1, 'second...
Prefer literals over
“constructors”
Method Time spent, 3 run minimum (ms)
dict() 376
Dictionary literals 135
Prefer slice notation over
“copy constructor”
l = [ 'a', 'b', 'c', 'd', 'e', 'f' ]
def a():
return list(l)
Prefer slice notation over
“copy constructor”
l = [ 'a', 'b', 'c', 'd', 'e', 'f' ]
def a():
return list(l)
def b():
return...
Prefer slice notation over
“copy constructor”
Method Time spent, 3 run minimum (ms)
Copy via list() 2671
Slice notation 16...
All functions have overhead
Function call overhead in Python is substantial

All functions can be redefined - even built-in...
String formatting
Python has a built-in function str() to convert
other types to string

In most cases this offers enough f...
String formatting
def a():
a = 5
b = 2
c = 3
return "%d" % (a*(b+c))
String formatting
def a():
a = 5
b = 2
c = 3
return "%d" % (a*(b+c))
def b():
a = 5
b = 2
c = 3
return str(a*(b+c))
String formatting
def a():
a = 5
b = 2
c = 3
return "%d" % (a*(b+c))
def c():
a = 5
b = 2
c = 3
return "%s" % (a*(b+c))
Method Time spent, 3 run minimum (ms)
“%d” 514
str() 260
“%s” 233
String formatting
Prefer aggregate functions
def a():
s = 0;
for i in range(50000):
s += i
return s
Prefer aggregate functions
def a():
s = 0;
for i in range(50000):
s += i
return s
def b():
return sum(range(50000))
Method Time spent, 3 run minimum (ms)
Summing manually 1728
Using sum() 587
Prefer aggregate functions
Prefer aggregate functions
Python has a number of built-in functions for
aggregates: all(), min(), max(), sum(), etc

Usin...
Use list comprehensions
def a():
l = []
for i in range(1000):
l.append(i)
return l
Use list comprehensions
def a():
l = []
for i in range(1000):
l.append(i)
return l
def b():
return [i for i in range(1000)]
Method Time spent, 3 run minimum (ms)
Append to list 701
List comprehension 321
Use list comprehensions
List comprehensions offer a concise way of
creating lists

Speed as well as readability advantages

Can be nested as well!
...
Don’t use optimisations
from other languages
def a():
x = 1;
for i in range(1000):
x = x + x
return x
Don’t use optimisations
from other languages
def b():
x = 1;
for i in range(1000):
x = x * 2
return x
Don’t use optimisations
from other languages
def c():
x = 1;
for i in range(1000):
x = x << 1
return x
Method Time spent, 3 run minimum (ms)
x + x 736
x * 2 1001
x << 1 1342
Don’t use optimisations
from other languages
LET’S TRY IT
PREPARE YOUR LAPTOPS!
PYTHON: WWW.CYBER-
DOJO.ORG 

E94905
C++:
git clone https://github.com/
avl7771/premature_optimization.git
Conclusions
Optimising is fun!

Knowledge about optimisations can help you help
your compiler or interpreter

Not all opti...
ARJAN VAN LEEUWEN
@AVL7771
Upcoming SlideShare
Loading in …5
×

Premature optimisation workshop

1,816 views

Published on

'Premature optimisation is the root of all evil', Donald Knuth told us in 1974. He was talking about the perceived trade-off between optimising performance and keeping code readable and maintainable. And we all know that we shouldn't even try optimising anything without measuring if there's an actual bottleneck in our product first. Or do we?

Making something faster, even at the micro-level, doesn't always mean that readability suffers. There are a lot of improvements that make code both more readable and better-performing, turning what might be called premature optimisation into just another healthy refactoring. And although some developers like to label almost all forms of optimisation as premature, this depends on a lot of factors, and designing for performance might actually be important for the product you are building. Let us also not forget that optimising, even prematurely, can be a lot of fun!

In this workshop we take a look at examples of optimisations that make sense to both the reader and the end-user of code. We also let ourselves go and prematurely optimise the heck out of some (C++ and Python) code, so make sure to bring your laptop if you have one!

Published in: Software
  • Be the first to comment

Premature optimisation workshop

  1. 1. ACCU 2015 PROJECT DATE CONFERENCE 23 APRIL PREMATURE OPTIMISATION WORKSHOP ARJAN VAN LEEUWEN
  2. 2. WWW.OPERA.COM JOIN THE COOL KIDS ON THE INFORMATION SUPERHIGHWAY
  3. 3. ACCU 2015 PROJECT DATE CONFERENCE 23 APRIL PREMATURE OPTIMISATION WORKSHOP ARJAN VAN LEEUWEN
  4. 4. A short conversation
  5. 5. OPTIMISING IS FUN AND KNOWING HOW TO DO IT CAN BE USEFUL
  6. 6. PREMATURE OPTIMISATION IS THE ROOT OF ALL EVIL DONALD KNUTH, “STRUCTURED PROGRAMMING WITH GOTO STATEMENTS”
  7. 7. PROGRAMMERS WASTE ENORMOUS AMOUNTS OF TIME THINKING ABOUT, OR WORRYING ABOUT, THE SPEED OF NONCRITICAL PARTS OF THEIR PROGRAMS, AND THESE ATTEMPTS AT EFFICIENCY ACTUALLY HAVE A STRONG NEGATIVE IMPACT WHEN DEBUGGING AND MAINTENANCE ARE CONSIDERED. WE SHOULD FORGET ABOUT SMALL EFFICIENCIES, SAY ABOUT 97% OF THE TIME: PREMATURE OPTIMISATION IS THE ROOT OF ALL EVIL. YET WE SHOULD NOT PASS UP OUR OPPORTUNITIES IN THAT CRITICAL 3%. “ ”
  8. 8. IN ESTABLISHED ENGINEERING DISCIPLINES A 12% IMPROVEMENT, EASILY OBTAINED, IS NEVER CONSIDERED MARGINAL AND I BELIEVE THE SAME VIEWPOINT SHOULD PREVAIL IN SOFTWARE ENGINEERING. “ ”
  9. 9. SMALL THINGS CAN MAKE A DIFFERENCE AND ARE WORTH STUDYING
  10. 10. Goals Find small changes that can make a difference Don’t sacrifice elegance for speed Give ideas on how to optimise
  11. 11. In the toolbox Common sense (doing nothing is always faster) Disassembler Time measurement Profiling tools
  12. 12. MICRO-OPTIMISATIONS IN C++
  13. 13. C++ Close to the metal Object model well-defined [Lippman96] Efficiency has been a major design goal for C++ from the beginning “You don’t pay for what you don’t use” Benefits from years of C optimisation experience
  14. 14. Branches Basis of much we do in imperative languages Compare and branch
  15. 15. if-else-if void GetAndProcessResult() { if (GetResult() == DOWNLOADED) return ProcessDownloadedFile(); else if (GetResult() == NEEDS_DOWNLOAD) return DownloadFile(); else if (GetResult() == NOT_AVAILABLE) return ReportNotAvailable(); else if (GetResult() == ERROR) return ReportError(); }
  16. 16. if-else-if void GetAndProcessResult() { const int result = GetResult(); if (result == DOWNLOADED) return ProcessDownloadedFile(); else if (result == NEEDS_DOWNLOAD) return DownloadFile(); else if (result == NOT_AVAILABLE) return ReportNotAvailable(); else if (result == ERROR) return ReportError(); }
  17. 17. if-else-if switch! void GetAndProcessResult() { switch (GetResult()) { case DOWNLOADED: return ProcessDownloadedFile(); case NEEDS_DOWNLOAD: return DownloadFile(); case NOT_AVAILABLE: return ReportNotAvailable(); case ERROR: return ReportError(); } }
  18. 18. The joys of switch Clarifies intention Clearer warnings / error messages Always allows compiler to create jump table or do binary search O(1) lookups
  19. 19. Jump table void GetAndProcessResult() { switch (GetResult()) { case DOWNLOADED: return ProcessDownloadedFile(); case NEEDS_DOWNLOAD: return DownloadFile(); case NOT_AVAILABLE: return ReportNotAvailable(); case ERROR: return ReportError(); } }
  20. 20. Jump table void GetAndProcessResult() { switch (GetResult()) { } } case 0: return ProcessDownloadedFile(); case 1: return DownloadFile(); case 2: return ReportNotAvailable(); case 3: return ReportError();
  21. 21. Jump table case 0: return ProcessDownloadedFile(); case 1: return DownloadFile(); case 2: return ReportNotAvailable(); case 3: return ReportError();
  22. 22. Jump table void GetAndProcessResult() { switch (GetResult()) { } } case 0: return ProcessDownloadedFile(); case 1: return DownloadFile(); case 2: return ReportNotAvailable(); case 3: return ReportError();
  23. 23. Jump table void GetAndProcessResult() { switch (GetResult()) { case 102: return ProcessDownloadedFile(); case 103: return DownloadFile(); case 104: return ReportNotAvailable(); case 105: return ReportError(); } }
  24. 24. Jump table void GetAndProcessResult() { switch (GetResult()) { case 102+0: return ProcessDownloadedFile(); case 102+1: return DownloadFile(); case 102+2: return ReportNotAvailable(); case 102+3: return ReportError(); } }
  25. 25. Jump table? void GetAndProcessResult() { switch (GetResult()) { case 1: return ProcessDownloadedFile(); case 16: return DownloadFile(); case 88: return ReportNotAvailable(); case 65536: return ReportError(); } }
  26. 26. Jump table Binary search void GetAndProcessResult() { switch (GetResult()) { case 1: return ProcessDownloadedFile(); case 16: return DownloadFile(); case 88: return ReportNotAvailable(); case 65536: return ReportError(); } } Compilers are smart
  27. 27. Predicting branches Predicting branches is hard Automated mechanisms (profile-guided optimisations) can offer big gains at the cost of having to profile your build If you’re very certain of your case, some compilers offer instructions such as __builtin_expect (gcc, clang)
  28. 28. Strings Most used and mis-used type in programming Mutable strings are the root of all evil
  29. 29. Strings misuse String is not a basic type A mutable string is a dynamic array of characters Almost anything you can do with a string is a function of the characters in that string Think about what will happen with long strings
  30. 30. Using std::string Be careful with modifying operations such as append() Avoid creating a string out of many parts, better to create at once Look into when alternative string types are useful
  31. 31. Growing strings std::string CopyString( const char* to_copy, size_t length) { std::string copied; for (size_t i = 0; i < length; i += BLOCKSIZE) copied.append(to_copy + i, std::min(BLOCKSIZE, length - i)); return copied; }
  32. 32. Growing strings std::string CopyString( const char* to_copy, size_t length) { std::stringstream copied; for (size_t i = 0; i < length; i += BLOCKSIZE) copied.write(to_copy + i, std::min(BLOCKSIZE, length - i)); return copied.str(); }
  33. 33. Growing strings std::string CopyString( const char* to_copy, size_t length) { std::string copied; copied.reserve(length); for (size_t i = 0; i < length; i += BLOCKSIZE) copied.append(to_copy + i, std::min(BLOCKSIZE, length - i)); return copied; }
  34. 34. Growing strings Method Time spent, 3 run average (ms) std::string::append() 1399 std::stringstream 5102 std::string::append() with std::string::reserve() 851
  35. 35. Converting numbers to strings and vice versa Can be a major source of slowness Often more features than needed Investigate alternative libraries (boost::spirit) Writing specialised functions a possibility (but with its own maintainability issues)
  36. 36. Integer-to-string conversion std::string Convert(int i) { std::stringstream stream; stream << i; return stream.str(); }
  37. 37. Integer-to-string conversion std::string Convert(int i) { return std::to_string(i); }
  38. 38. Integer-to-string conversion std::string Convert(int i) { namespace karma = boost::spirit::karma; std::string converted; std::back_insert_iterator<std::string> sink(converted); karma::generate(sink, karma::int_, i); return converted; }
  39. 39. Integer-to-string conversion Method Time spent, 3 run average (ms) std::stringstream 2959 std::to_string 1012 boost::spirit::karma 332
  40. 40. String-to-integer conversion int Convert(const std::string& str) { return std::stoi(str); }
  41. 41. String-to-integer conversion int Convert(const std::string& str) { namespace qi = boost::spirit::qi; int converted; qi::parse(str.begin(), str.end(), qi::int_, converted); return converted; }
  42. 42. String-to-integer conversion Method Time spent, 3 run average (ms) std::stoi 3920 boost::spirit::qi 1276
  43. 43. Function calls Function calls have overhead Lookup in virtual function table Setting up stack, restoring stack
  44. 44. Avoiding virtual functions or virtual function calls Only declare functions (this includes destructors) virtual when it’s actually needed Don’t use virtual functions for types that are handled by value If type is known, no lookup is needed Sometimes compile-time polymorphism offers an alternative
  45. 45. Avoiding function calls For small functions called in tight loops, inlining helps Allow the compiler to inline functions where it makes sense (have definition available) If the compiler doesn’t co-operate and you’re sure it makes sense (measure this), force it
  46. 46. Tail calls A tail call happens when a function is the final call made in another function Tail calls can be eliminated, so that they end up being a jump construction Eliminates call overhead Be aware of this and create tail calls where possible Also allows efficient recursive functions
  47. 47. Facilitating tail calls unsigned djb_hash(const char* string) { int c = *string; if (!c) return 5381; return djb_hash(string + 1) * 33 + c; }
  48. 48. Facilitating tail calls unsigned djb_hash( const char* string, unsigned seed) { int c = *string; if (!c) return seed; return djb_hash( string + 1, seed * 33 + c); }
  49. 49. Facilitating tail calls Method Time spent, 3 run average (ms) Tail call elimination not possible 2274 Tail call elimination possible 1097
  50. 50. Use lambda functions C++11 lambdas can always be trivially inlined, unlike function pointers Offers an elegant and fast way of processing data Combines well with aggregate functions
  51. 51. Use lambda functions void twice(int& value) { value *= 2; } std::vector<int> EverythingTwice( const std::vector<int>& original) { std::vector<int> result(original); std::for_each(result.begin(), result.end(), &twice); return result; }
  52. 52. Use lambda functions std::vector<int> EverythingTwice2( const std::vector<int>& original) { std::vector<int> result(original); std::for_each(result.begin(), result.end(), [](int& value){ value *= 2; }); return result; }
  53. 53. Use lambda functions Method Time spent, 3 run average (ms) Function pointer (not inlined) 1684 Lambda function (inlined) 220
  54. 54. Return-value optimisation Allows the compiler to avoid copy construction on temporaries Executed by compilers when function returns one named variable Be aware of where it could be possible, allow the compiler to help you But sometimes it’s more helpful to implement…
  55. 55. Move semantics User defines for movable types how they can be moved correctly ‘Guaranteed’ way of getting return value optimisation Helpful in combination with std::vector (to keep data local) Can come for free using “Rule of zero”
  56. 56. Move semantics class Typical { public: Typical() : content_("this is a typical string") {} Typical(const Typical& other) : content_(other.content_) {} private: std::string content_; };
  57. 57. Move semantics class Typical { public: TypicalMove () : content_("this is a typical string") {} private: std::string content_; };
  58. 58. Move semantics std::vector<Typical> CreateTypical() { std::vector<Typical> new_content; for (int i = 0; i < 1024; ++i) new_content.push_back(Typical()); return new_content; }
  59. 59. Move semantics Method Time spent, 3 run average (ms) With copy constructor 2617 Following “Rule of zero” 1002
  60. 60. Data Make sure that all data you need in a loop is physically as close together as possible Allows CPU to use its cache efficiently Use contiguous memory arrays where possible Avoid data structures that rely on pointers (eg. linked lists)
  61. 61. Data int sum() { std::forward_list<int> data(1024, 5); int result; for (int i = 0; i < 1000000; ++i) { result = std::accumulate( data.begin(), data.end(), 0); } return result; }
  62. 62. Data int sum() { std::vector<int> data(1024, 5); int result; for (int i = 0; i < 1000000; ++i) { result = std::accumulate( data.begin(), data.end(), 0); } return result; }
  63. 63. Data Method Time spent, 3 run average (ms) std::forward_list 1115 std::vector 61
  64. 64. MICRO-OPTIMISATIONS IN PYTHON
  65. 65. Python Emphasises readability Dynamic type system, automatic memory management Several projects dedicated to improving performance Always try to avoid calling functions many times
  66. 66. Prefer literals over “constructors” def a(): return dict(firstkey=1, secondkey=2)
  67. 67. Prefer literals over “constructors” def a(): return dict(firstkey=1, secondkey=2) def b(): return { 'firstkey': 1, 'secondkey': 2 }
  68. 68. Prefer literals over “constructors” Method Time spent, 3 run minimum (ms) dict() 376 Dictionary literals 135
  69. 69. Prefer slice notation over “copy constructor” l = [ 'a', 'b', 'c', 'd', 'e', 'f' ] def a(): return list(l)
  70. 70. Prefer slice notation over “copy constructor” l = [ 'a', 'b', 'c', 'd', 'e', 'f' ] def a(): return list(l) def b(): return l[:]
  71. 71. Prefer slice notation over “copy constructor” Method Time spent, 3 run minimum (ms) Copy via list() 2671 Slice notation 1679
  72. 72. All functions have overhead Function call overhead in Python is substantial All functions can be redefined - even built-ins need to be looked up first Try to avoid function calls (even more so than in
 C++) Using literals or other built-in constructs can help avoid function calls
  73. 73. String formatting Python has a built-in function str() to convert other types to string In most cases this offers enough features for conversions of types to strings Faster than formatting
  74. 74. String formatting def a(): a = 5 b = 2 c = 3 return "%d" % (a*(b+c))
  75. 75. String formatting def a(): a = 5 b = 2 c = 3 return "%d" % (a*(b+c)) def b(): a = 5 b = 2 c = 3 return str(a*(b+c))
  76. 76. String formatting def a(): a = 5 b = 2 c = 3 return "%d" % (a*(b+c)) def c(): a = 5 b = 2 c = 3 return "%s" % (a*(b+c))
  77. 77. Method Time spent, 3 run minimum (ms) “%d” 514 str() 260 “%s” 233 String formatting
  78. 78. Prefer aggregate functions def a(): s = 0; for i in range(50000): s += i return s
  79. 79. Prefer aggregate functions def a(): s = 0; for i in range(50000): s += i return s def b(): return sum(range(50000))
  80. 80. Method Time spent, 3 run minimum (ms) Summing manually 1728 Using sum() 587 Prefer aggregate functions
  81. 81. Prefer aggregate functions Python has a number of built-in functions for aggregates: all(), min(), max(), sum(), etc Using them brings big speed advantages Always preferred over manually iterating
  82. 82. Use list comprehensions def a(): l = [] for i in range(1000): l.append(i) return l
  83. 83. Use list comprehensions def a(): l = [] for i in range(1000): l.append(i) return l def b(): return [i for i in range(1000)]
  84. 84. Method Time spent, 3 run minimum (ms) Append to list 701 List comprehension 321 Use list comprehensions
  85. 85. List comprehensions offer a concise way of creating lists Speed as well as readability advantages Can be nested as well! Use list comprehensions
  86. 86. Don’t use optimisations from other languages def a(): x = 1; for i in range(1000): x = x + x return x
  87. 87. Don’t use optimisations from other languages def b(): x = 1; for i in range(1000): x = x * 2 return x
  88. 88. Don’t use optimisations from other languages def c(): x = 1; for i in range(1000): x = x << 1 return x
  89. 89. Method Time spent, 3 run minimum (ms) x + x 736 x * 2 1001 x << 1 1342 Don’t use optimisations from other languages
  90. 90. LET’S TRY IT PREPARE YOUR LAPTOPS!
  91. 91. PYTHON: WWW.CYBER- DOJO.ORG 
 E94905 C++: git clone https://github.com/ avl7771/premature_optimization.git
  92. 92. Conclusions Optimising is fun! Knowledge about optimisations can help you help your compiler or interpreter Not all optimisations worsen maintainability Micro-optimisations can differ between languages, compilers, architectures… Measuring works! Test your assumptions
  93. 93. ARJAN VAN LEEUWEN @AVL7771

×