Repository: Parquery/icontract

Issue:
Performance impact of contracts particularly high
I expected a substantial performance impact by using contracts, but the impact of invariants is huge, even with the simplest predicate. This is especially true for the latest version.

### Evaluation

Here is a small test program I used to evaluate the performance:

```Python
from icontract import invariant

@invariant(lambda self: True)
class A:
    def __init__(self, identifier: str) -> None:
        self.parts = identifier.split(".")

class B:
    def __init__(self, identifier: str) -> None:
        self.parts = identifier.split(".")

if __name__ == "__main__":
    import timeit

    print(timeit.timeit("A('X.Y')", setup="from __main__ import A", number=10000000))
    print(timeit.timeit("B('X.Y')", setup="from __main__ import B", number=10000000))
```

### Results

#### icontract 2.3.2

36.290041845990345
2.4590219910023734

#### icontract 2.2.0
              
9.24365290501737
2.4601732709852513

#### dpcontracts 0.6.0

4.568975409987615
2.543887578009162

Just for comparison I run the same test with dpcontracts.

Discussion:
coveralls: ## Pull Request Test Coverage Report for [Build 363](https://coveralls.io/builds/30889505)

* **16** of **16**   **(100.0%)**  changed or added relevant lines in **2** files are covered.
* **3** unchanged lines in **2** files lost coverage.
* Overall coverage decreased (**-0.3%**) to **94.145%**

---


|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [icontract/_checkers.py](https://coveralls.io/builds/30889505/source?filename=icontract%2F_checkers.py#L564) | 1 | 97.83% |
| [icontract/_recompute.py](https://coveralls.io/builds/30889505/source?filename=icontract%2F_recompute.py#L364) | 2 | 88.89% |
<!-- | **Total:** | **3** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/30889505/badge)](https://coveralls.io/builds/30889505) |
| :-- | --: |
| Change from base [Build 361](https://coveralls.io/builds/30750363): |  -0.3% |
| Covered Lines: | 997 |
| Relevant Lines: | 1059 |

---
##### 💛  - [Coveralls](https://coveralls.io)

mristin: Hi @treiher,
Could you please review the benchmarks for me:
* benchmarks/against_dpcontracts/compare_invariant.py
* benchmarks/against_dpcontracts/compare_precondition.py and
* benchmarks/against_dpcontracts/compare_postcondition.py
?

The results on my computer look more or less in line with what you reported in #142, but I get very wild overhead by dpcontracts in preconditions and postconditions.

You can see the results in README.rst as a series of tables.

treiher: > Hi @treiher,
> Could you please review the benchmarks for me:
> 
> * benchmarks/against_dpcontracts/compare_invariant.py
> * benchmarks/against_dpcontracts/compare_precondition.py and
> * benchmarks/against_dpcontracts/compare_postcondition.py
>   ?

The benchmarks look correct to me.

> The results on my computer look more or less in line with what you reported in #142, but I get very wild overhead by dpcontracts in preconditions and postconditions.
> 
> You can see the results in [README.rst](https://github.com/Parquery/icontract/blob/43a2e902f6087991530f46c7a654cb836cb84111/README.rst#benchmarks) as a series of tables.
> 
> It would be great help if you could check out the branch and run the scripts on your computer. 

Here are my results (executed on a Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz):

```Console
Benchmarking invariant:

=========================  ============  ==============  =======================
Case                         Total time    Time per run    Relative time per run
=========================  ============  ==============  =======================
`ClassWithIcontract`             1.13 s         1.13 μs                     325%
`ClassWithDpcontracts`           0.35 s         0.35 μs                     100%
`ClassWithInlineContract`        0.21 s         0.21 μs                      61%
`ClassWithoutContracts`          0.17 s         0.17 μs                      48%
=========================  ============  ==============  =======================

Benchmarking precondition:

===============================  ============  ==============  =======================
Case                               Total time    Time per run    Relative time per run
===============================  ============  ==============  =======================
`function_with_icontract`              0.02 s         1.88 μs                       4%
`function_with_dpcontracts`            0.42 s        42.36 μs                     100%
`function_with_inline_contract`        0.00 s         0.08 μs                       0%
`function_without_contracts`           0.00 s         0.07 μs                       0%
===============================  ============  ==============  =======================

Benchmarking postcondition:

===============================  ============  ==============  =======================
Case                               Total time    Time per run    Relative time per run
===============================  ============  ==============  =======================
`function_with_icontract`              0.02 s         2.39 μs                       6%
`function_with_dpcontracts`            0.42 s        42.07 μs                     100%
`function_with_inline_contract`        0.00 s         0.09 μs                       0%
`function_without_contracts`           0.00 s         0.08 μs                       0%
===============================  ============  ==============  =======================
```

I would say my results are in line with yours.

> Any clue why dpcontracts are so slow in precondition and postcondition?

Probably because of https://github.com/deadpixi/contracts/issues/20.

---

```Python
import icontract

class NoContract:
    def __init__(self, identifier: str) -> None:
        self.parts = identifier.split(".")

@icontract.invariant(lambda self: True)
class IInv:
    def __init__(self, identifier: str) -> None:
        self.parts = identifier.split(".")

class IPre:
    @icontract.require(lambda self: True)
    def __init__(self, identifier: str) -> None:
        self.parts = identifier.split(".")

class IPost:
    @icontract.ensure(lambda self: True)
    def __init__(self, identifier: str) -> None:
        self.parts = identifier.split(".")

if __name__ == "__main__":
    import timeit

    print("no contract", timeit.timeit("NoContract('X.Y')", setup="from __main__ import NoContract", number=10000000))
    print("invariant", timeit.timeit("IInv('X.Y')", setup="from __main__ import IInv", number=10000000))
    print("precondition", timeit.timeit("IPre('X.Y')", setup="from __main__ import IPre", number=10000000))
    print("postcondition", timeit.timeit("IPost('X.Y')", setup="from __main__ import IPost", number=10000000))
```

```Console
no contract 1.759606615640223
invariant 10.688277382403612
precondition 20.638870515860617
postcondition 25.76885095331818
```

The results are much better than before. But I'm wondering why a precondition or postcondition at a constructor is slower than an class invariant. I would have thought an invariant would be more complex. What could be the reason for that?
mristin: Hi @treiher ,
I added one more benchmark (invariant at a function to check that it doesn't misbehave). Everything seems OK. Could you please have another look at the [readme](https://github.com/Parquery/icontract/pull/143/commits/3d8b97a4e3bdf49cde2afc04c9c8cd297894ff53#diff-88b99bb28683bd5b7e3a204826ead112)?

> The results are much better than before. But I'm wondering why a precondition or postcondition at a constructor is slower than an class invariant. I would have thought an invariant would be more complex. What could be the reason for that?

I think that the invariants are actually simpler -- they only require the argument `self` which you directly supply, while both dpcontracts and icontract need to pass over the arguments (and icontract additionally picks the subset). That is actually much more involving than just passing the instance as `self`. Dictionary look-ups are expensive in Python...

After your review should we merge the PR? Or is the performance in your opinion still unacceptable for practical purposes?
treiher: > Hi @treiher ,
> I added one more benchmark (invariant at a function to check that it doesn't misbehave). Everything seems OK. Could you please have another look at the [readme](https://github.com/Parquery/icontract/pull/143/commits/3d8b97a4e3bdf49cde2afc04c9c8cd297894ff53#diff-88b99bb28683bd5b7e3a204826ead112)?

Looks good to me.

> I think that the invariants are actually simpler -- they only require the argument `self` which you directly supply, while both dpcontracts and icontract need to pass over the arguments (and icontract additionally picks the subset). That is actually much more involving than just passing the instance as `self`.

That makes sense. Thanks for the explanation.

> After your review should we merge the PR? Or is the performance in your opinion still unacceptable for practical purposes?

I'm currently evaluating the use of icontract for [RecordFlux](https://github.com/Componolit/RecordFlux). As a first step I added some invariants and preconditions which were realized by assertions previously. Even this small number of contracts has a significant impact on the runtime of our test suite: 2:42 min without contracts vs. 5:22 min with contracts (measured with the optimized version of this PR). Waiting additional 2 min for running the unit tests can be quite annoying during develoment. This will probably get even worse when more contracts are added, which we plan to do (especially for [property-based testing](https://hillelwayne.com/talks/beyond-unit-tests/)). So I tend to say the performance is still unacceptable for practical purposes. That is very unfortunate, because the use of invariants and inherited contracts is really nice. We will probably use it for the time being despite its performance impact, but I'm not sure we will in the long run.

So I see no reason to not merge the PR at the moment. It already leads to a significantly better runtime. But I think still a better performance would be needed in the long term. Do you think that's possible?
mristin: Hi @treiher ,

> So I tend to say the performance is still unacceptable for practical purposes. That is very unfortunate, because the use of invariants and inherited contracts is really nice. We will probably use it for the time being despite its performance impact, but I'm not sure we will in the long run.

Thanks for the feedback! I spent some 2 hours today trying to optimize the checker, but to no avail. Python is slow at dictionary look-ups and object creation and the whole procedure involves many of them to cover all the edge cases. I tried sacrificing the readability and in-lining, but that didn't bring about any tangible improvement.

I arbitrarily tried to optimize for postconditions and examine `iconract._checkers.decorate_with_checker` block by block, measure the performance and see what can be improved. Unfortunately, pretty all of the pieces are necessary to cover the edge cases. For example:
* Resolving arguments can't be simplified further since you need to take into account the default values. 
* The `setattr` and `getattr` on the thread local storage is necessary to prevent endless recursion in a thread-safe manner.
* Some checks are necessary in order not to puzzle the user (*e.g.*, `result` argument of the function conflicting with the result argument of the condition)
* You need to check which condition arguments are missing in order to give a meaningful error.
* Icontract picks a subset of arguments, but this was not really a big computational sink.
* You need to use a custom `not` check, otherwise you get unexpected errors on numpy.
* *Etc.*

I am indeed a bit frustrated with hitting the wall. I even doubt that the standard trick, C/C++ rewrite, would bring a substantial speed-up (and wouldn't work on PyPy and similar Python distributions).

If you have some spare time, please have a look at the checker: 
https://github.com/Parquery/icontract/pull/143/files#diff-edca2db3385a27c160aeeb96f34ce1d4L341

Maybe you see something that I simply didn't? (Please let me know how much time to wait till we merge this PR in; if you have no time for it, we can merge it straight away.)

In the end, DbC should be integrated into the language and the interpreter should take care of all this. A library will be always inherently slow :-(.
mristin: @treiher Btw., I'm thinking about implementing a tool for Python similar to  https://github.com/Parquery/gocontracts. It would make sure that the docstrings coincide with code. That would be much faster than icontract and could still support the inheritance.

Would you have some 30 minutes to discuss the idea with a shared screen?
treiher: > I spent some 2 hours today trying to optimize the checker, but to no avail. Python is slow at dictionary look-ups and object creation and the whole procedure involves many of them to cover all the edge cases. I tried sacrificing the readability and in-lining, but that didn't bring about any tangible improvement.

Thanks for your detailed analysis! Too bad that there seems to be no possibility to improve the performance. The integration of DbC into the language would certainly be the best solution, but this will most likely not happen in the near future.

I think this PR can then be merged. I may be able to take a deeper look at the implementation at some point, but I cannot promise anything yet.

> Btw., I'm thinking about implementing a tool for Python similar to https://github.com/Parquery/gocontracts. It would make sure that the docstrings coincide with code. That would be much faster than icontract and could still support the inheritance.
> 
> Would you have some 30 minutes to discuss the idea with a shared screen?

Sure, that sounds interesting.You can also contact me directly to arrange this (you can find my e-mail address in the [RecordFlux](https://github.com/Componolit/RecordFlux) repo).
mristin: @treiher great, I'll write you an email!

@Sasha93 this PR can be merged the. Let's also bump & release a patch version (this gives a 2x speed-up!).

Patch:
diff --git a/README.rst b/README.rst
index f6174f3..4a183af 100644
--- a/README.rst
+++ b/README.rst
@@ -759,168 +759,83 @@ worked fine on our code base.
 
 Benchmarks
 ==========
-We evaluated the computational costs incurred by using icontract in a couple of experiments. There are basically two
-types of costs caused by icontract: 1) extra parsing performed when you import the module regardless whether conditions
-are switched on or off and 2) run-time cost of verifying the contract.
+We run benchmarks against dpcontracts as part of our continuous integration.
 
-We assumed in all the experiments that the contract breaches are exceptionally seldom and thus need not to be analyzed.
-If you are interested in these additional expereminets, please do let us know and create an issue on the github page.
+The bodies of the constructors and functions were intentionally left simple so that you can
+better estimate **overhead** of the contracts in absolute terms rather than relative.
+This means that the code without contracts will run extremely fast (nanoseconds) in the benchmarks
+which might make the contracts seem sluggish. However, the methods in the real world usually run
+in the order of microseconds and milliseconds, not nanoseconds. Hence, as long as the overhead
+of the contract is in the order of microseconds, it is practically acceptable.
 
-The source code of the benchmarks is available in
-`this directory <https://github.com/Parquery/icontract/tree/master/benchmarks>`_.
-All experiments were performed with Python 3.5.2+ on Intel(R) Core(TM) i7-4700MQ CPU @ 2.40GHz with 8 cores and
-4 GB RAM.
+.. Becnhmark report from precommit.py starts.
 
-We execute a short timeit snippet to bring the following measurements into context:
 
-.. code-block:: bash
-
-    python3 -m timeit -s 'import math' 'math.sqrt(1984.1234)'
-    10000000 loops, best of 3: 0.0679 usec per loop
-
-Import Cost
------------
-**Pre and post-conditions.**
-We generated a module with 100 functions. For each of these functions, we introduced a variable number of conditions and
-stop-watched how long it takes to import the module over 10 runs. Finally, we compared the import time of the module
-with contracts against the same module without any contracts in the code.
-
-The overhead is computed as the time difference between the total import time compared to the total import time of the
-module without the contracts.
-
-+-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
-| Number of conditions per function | Total import time [milliseconds] | Overhead [milliseconds] | Overhead per condition and function [milliseconds] |
-+===================================+==================================+=========================+====================================================+
-| None                              |                   795.59 ± 10.47 |                     N/A |                                                N/A |
-+-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
-| 1                                 |                   919.53 ± 61.22 |                  123.93 |                                               1.24 |
-+-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
-| 5                                 |                  1075.81 ± 59.87 |                  280.22 |                                               0.56 |
-+-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
-| 10                                |                  1290.22 ± 90.04 |                  494.63 |                                               0.49 |
-+-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
-| 1 disabled                        |                   833.60 ± 32.07 |                   37.41 |                                               0.37 |
-+-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
-| 5 disabled                        |                   851.31 ± 66.93 |                   55.72 |                                               0.11 |
-+-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
-| 10 disabled                       |                  897.90 ± 143.02 |                  101.41 |                                               0.10 |
-+-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
-
-As we can see in the table above, the overhead per condition is quite minimal (1.24 milliseconds in case of a single
-enabled contract). The overhead decreases as you add more conditions since the icontract has already initialized all the
-necessary fields in the function objects.
-
-When you disable the conditions, there is much less overhead (only 0.37 milliseconds, and decreasing). Since icontract
-returns immediately when the condition is disabled by implementation, we assume that the overhead coming from disabled
-conditions is mainly caused by Python interpreter parsing the code.
-
-
-**Invariants**. Analogously, to measure the overhead of the invariants, we generated a module with 100 classes.
-We varied the number of invariant conditions per class and measure the import time over 10 runs. The results
-are presented in the following table.
-
-+--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
-| Number of conditions per class | Total import time [milliseconds] | Overhead [milliseconds] | Overhead per condition and class [milliseconds] |
-+================================+==================================+=========================+=================================================+
-| None                           |                   843.61 ± 28.21 |                     N/A |                                             N/A |
-+--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
-| 1                              |                  3409.71 ± 95.78 |                  2566.1 |                                           25.66 |
-+--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
-| 5                              |                 4005.93 ± 131.97 |                 3162.32 |                                            6.32 |
-+--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
-| 10                             |                 4801.82 ± 157.56 |                 3958.21 |                                            3.96 |
-+--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
-| 1 disabled                     |                   885.88 ± 44.24 |                   42.27 |                                            0.42 |
-+--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
-| 5 disabled                     |                  912.53 ± 101.91 |                   68.92 |                                            0.14 |
-+--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
-| 10 disabled                    |                  963.77 ± 161.76 |                  120.16 |                                            0.12 |
-+--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
-
-Similar to pre and post-conditions, there is decreasing cost per condition as number of conditions increases since the
-icontract has all set up the fields in the class metadata. However, invariants are much more costly compared to pre and
-postconditions and their overhead should be considered if import time needs to be kept minimal.
-
-The overhead of the disabled invariants is attributed to the overhead of parsing the source file as icontract
-immediately returns from disabled invariants by implementation.
-
-Run-time Cost
--------------
-We constructed four modules around a function which computes a square root. We used ``timeit`` module to measure the
-performance. Each module was imported as part of the setup.
-
-**No precondition**. This module does not check any preconditions and serves as a baseline.
+The following scripts were run:
 
-.. code-block: python
+* `benchmarks/against_dpcontracts/compare_invariant.py <https://github.com/Parquery/icontract/tree/master/benchmarks/against_dpcontracts/compare_invariant.py>`_
+* `benchmarks/against_dpcontracts/compare_precondition.py <https://github.com/Parquery/icontract/tree/master/benchmarks/against_dpcontracts/compare_precondition.py>`_
+* `benchmarks/against_dpcontracts/compare_postcondition.py <https://github.com/Parquery/icontract/tree/master/benchmarks/against_dpcontracts/compare_postcondition.py>`_
 
-    import math
-    def my_sqrt(x: float) -> float:
-        return math.sqrt(x)
+The benchmarks were executed on Intel(R) Core(TM) i7-4700MQ CPU @ 2.40GHz.
+We used icontract 2.3.2.post1 and dpcontracts 0.6.0.
 
-**Pre-condition as assert**. We check pre-conditions by an assert statement.
-
-.. code-block:: python
+The following tables summarize the results.
 
-    def my_sqrt(x: float) -> float:
-        assert x >= 0
-        return math.sqrt(x)
+Benchmarking invariant at __init__:
 
-**Pre-condition as separate function**. To mimick a more complex pre-condition, we encapsulate the assert in a separate
-function.
+=========================  ============  ==============  =======================
+Case                         Total time    Time per run    Relative time per run
+=========================  ============  ==============  =======================
+`ClassWithIcontract`             2.14 s         2.14 μs                     314%
+`ClassWithDpcontracts`           0.68 s         0.68 μs                     100%
+`ClassWithInlineContract`        0.42 s         0.42 μs                      62%
+=========================  ============  ==============  =======================
 
-.. code-block:: python
-
-    import math
-
-    def check_non_negative(x: float) -> None:
-        assert x >= 0
-
-    def my_sqrt(x: float) -> float:
-        check_non_negative(x)
-        return math.sqrt(x)
-
-**Pre-condition with icontract**. Finally, we compare against a module that uses icontract.
-
-.. code-block:: python
+Benchmarking invariant at a function:
 
-    import math
-    import icontract
+=========================  ============  ==============  =======================
+Case                         Total time    Time per run    Relative time per run
+=========================  ============  ==============  =======================
+`ClassWithIcontract`             2.91 s         2.91 μs                     445%
+`ClassWithDpcontracts`           0.65 s         0.65 μs                     100%
+`ClassWithInlineContract`        0.33 s         0.33 μs                      51%
+=========================  ============  ==============  =======================
 
-    @icontract.require(lambda x: x >= 0)
-    def my_sqrt(x: float) -> float:
-        return math.sqrt(x)
+Benchmarking precondition:
 
-**Pre-condition with icontract, disabled**. We also perform a redundant check to verify that a disabled condition
-does not incur any run-time cost.
+===============================  ============  ==============  =======================
+Case                               Total time    Time per run    Relative time per run
+===============================  ============  ==============  =======================
+`function_with_icontract`              0.03 s         3.46 μs                       1%
+`function_with_dpcontracts`            2.85 s       285.11 μs                     100%
+`function_with_inline_contract`        0.00 s         0.14 μs                       0%
+===============================  ============  ==============  =======================
 
-The following table sumarizes the timeit results (10000000 loops, best of 3). The run time of the baseline is computed
-as:
+Benchmarking postcondition:
 
-.. code-block: bash
+===============================  ============  ==============  =======================
+Case                               Total time    Time per run    Relative time per run
+===============================  ============  ==============  =======================
+`function_with_icontract`              0.03 s         3.38 μs                       1%
+`function_with_dpcontracts`            2.84 s       283.59 μs                     100%
+`function_with_inline_contract`        0.00 s         0.14 μs                       0%
+===============================  ============  ==============  =======================
 
-    $ python3 -m timeit -s 'import my_sqrt' 'my_sqrt.my_sqrt(1984.1234)'
 
-The run time of the other functions is computed analogously.
 
-+---------------------+-------------------------+
-| Precondition        | Run time [microseconds] |
-+=====================+=========================+
-| None                |                   0.168 |
-+---------------------+-------------------------+
-| As assert           |                   0.203 |
-+---------------------+-------------------------+
-| As function         |                   0.274 |
-+---------------------+-------------------------+
-| icontract           |                    2.78 |
-+---------------------+-------------------------+
-| icontract, disabled |                   0.165 |
-+---------------------+-------------------------+
+.. Benchmark report from precommit.py ends.
 
-The overhead of icontract is substantial. While this may be prohibitive for points where computational efficiency is
-more important than correctness, mind that the overhead is still in order of microseconds. In most practical scenarios,
-where a function is more complex and takes longer than a few microseconds to execute, such a tiny overhead is
-justified by the gains in correctness, development and maintenance time.
+We also ran a much more extensive battery of benchmarks on icontract 2.0.7. Unfortunately,
+it would cost us too much effort to integrate the results in the continous integration.
+The report is available at:
+`benchmarks/benchmark_2.0.7.rst <https://github.com/Parquery/icontract/tree/master/benchmarks/benchmark_2.0.7.rst>`_.
 
+The scripts are available at:
+`benchmarks/import_cost/ <https://github.com/Parquery/icontract/tree/master/benchmarks/import_cost>`_
+and
+`benchmarks/runtime_cost/ <https://github.com/Parquery/icontract/tree/master/benchmarks/runtime_cost>`_.
+Please re-run the scripts manually to obtain the results with the latest icontract version.
 
 Installation
 ============
diff --git a/benchmarks/against_dpcontracts/compare_invariant.py b/benchmarks/against_dpcontracts/compare_invariant.py
new file mode 100644
index 0000000..68e3dae
--- /dev/null
+++ b/benchmarks/against_dpcontracts/compare_invariant.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python3
+
+"""
+Benchmark icontract against dpcontracts and no contracts.
+
+The benchmark was supplied by: https://github.com/Parquery/icontract/issues/142
+"""
+import timeit
+from typing import List
+
+import dpcontracts
+import tabulate
+
+import icontract
+
+
+@icontract.invariant(lambda self: len(self.parts) > 0)
+class ClassWithIcontract:
+    def __init__(self, identifier: str) -> None:
+        self.parts = identifier.split(".")
+
+    def some_func(self) -> str:
+        return '.'.join(self.parts)
+
+
+@dpcontracts.invariant("some dummy invariant", lambda self: len(self.parts) > 0)
+class ClassWithDpcontracts:
+    def __init__(self, identifier: str) -> None:
+        self.parts = identifier.split(".")
+
+    def some_func(self) -> str:
+        return '.'.join(self.parts)
+
+
+class ClassWithInlineContract:
+    def __init__(self, identifier: str) -> None:
+        self.parts = identifier.split(".")
+        assert len(self.parts) > 0
+
+    def some_func(self) -> str:
+        assert len(self.parts) > 0
+        result = '.'.join(self.parts)
+        assert len(self.parts) > 0
+        return result
+
+
+# dpcontracts change __name__ attribute of the class, so we can not use
+# ClassWithDpcontractsInvariant.__name__ for a more maintainable list.
+clses = [
+    'ClassWithIcontract',
+    'ClassWithDpcontracts',
+    'ClassWithInlineContract',
+]
+
+
+def measure_invariant_at_init() -> None:
+    durations = [0.0] * len(clses)
+
+    number = 1 * 1000 * 1000
+
+    for i, cls in enumerate(clses):
+        duration = timeit.timeit(
+            "{}('X.Y')".format(cls),
+            setup="from __main__ import {}".format(cls),
+            number=number)
+        durations[i] = duration
+
+    print("Benchmarking invariant at __init__:\n")
+    table = []  # type: List[List[str]]
+
+    for cls, duration in zip(clses, durations):
+        # yapf: disable
+        table.append([
+            '`{}`'.format(cls),
+            '{:.2f} s'.format(duration),
+            '{:.2f} μs'.format(duration * 1000 * 1000 / number),
+            '{:.0f}%'.format(duration * 100 / durations[1])
+        ])
+        # yapf: enable
+
+    # yapf: disable
+    table_str = tabulate.tabulate(
+        table,
+        headers=['Case', 'Total time', 'Time per run', 'Relative time per run'],
+        colalign=('left', 'right', 'right', 'right'),
+        tablefmt='rst')
+    # yapf: enable
+
+    print(table_str)
+
+
+def measure_invariant_at_function() -> None:
+    durations = [0.0] * len(clses)
+
+    number = 1 * 1000 * 1000
+
+    for i, cls in enumerate(clses):
+        duration = timeit.timeit(
+            "a.some_func()",
+            setup="from __main__ import {0}; a = {0}('X.Y')".format(cls),
+            number=number)
+        durations[i] = duration
+
+    print("Benchmarking invariant at a function:\n")
+    table = []  # type: List[List[str]]
+
+    for cls, duration in zip(clses, durations):
+        # yapf: disable
+        table.append([
+            '`{}`'.format(cls),
+            '{:.2f} s'.format(duration),
+            '{:.2f} μs'.format(duration * 1000 * 1000 / number),
+            '{:.0f}%'.format(duration * 100 / durations[1])
+        ])
+        # yapf: enable
+
+    # yapf: disable
+    table_str = tabulate.tabulate(
+        table,
+        headers=['Case', 'Total time', 'Time per run', 'Relative time per run'],
+        colalign=('left', 'right', 'right', 'right'),
+        tablefmt='rst')
+    # yapf: enable
+
+    print(table_str)
+
+
+if __name__ == "__main__":
+    measure_invariant_at_init()
+    print()
+    measure_invariant_at_function()
diff --git a/benchmarks/against_dpcontracts/compare_postcondition.py b/benchmarks/against_dpcontracts/compare_postcondition.py
new file mode 100644
index 0000000..0dd7e69
--- /dev/null
+++ b/benchmarks/against_dpcontracts/compare_postcondition.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+
+"""
+Benchmark icontract against dpcontracts and no contracts.
+
+The benchmark was supplied by: https://github.com/Parquery/icontract/issues/142
+"""
+import math
+import timeit
+from typing import List
+
+import tabulate
+
+import icontract
+import dpcontracts
+
+
+@icontract.ensure(lambda result: result > 0)
+def function_with_icontract(someArg: int) -> float:
+    return math.sqrt(someArg)
+
+
+@dpcontracts.ensure("some dummy contract", lambda args, result: result > 0)
+def function_with_dpcontracts(someArg: int) -> float:
+    return math.sqrt(someArg)
+
+
+def function_with_inline_contract(someArg: int) -> float:
+    result = math.sqrt(someArg)
+    assert result > 0
+    return result
+
+
+def measure_functions() -> None:
+    funcs = [
+        'function_with_icontract',
+        'function_with_dpcontracts',
+        'function_with_inline_contract'
+    ]
+
+    durations = [0.0] * len(funcs)
+
+    number = 10 * 1000
+
+    for i, func in enumerate(funcs):
+        duration = timeit.timeit(
+            "{}(198.4)".format(func),
+            setup="from __main__ import {}".format(func),
+            number=number)
+        durations[i] = duration
+
+    table = []  # type: List[List[str]]
+
+    for func, duration in zip(funcs, durations):
+        # yapf: disable
+        table.append([
+            '`{}`'.format(func),
+            '{:.2f} s'.format(duration),
+            '{:.2f} μs'.format(duration * 1000 * 1000 / number),
+            '{:.0f}%'.format(duration * 100 / durations[1])
+        ])
+        # yapf: enable
+
+    # yapf: disable
+    table_str = tabulate.tabulate(
+        table,
+        headers=['Case', 'Total time', 'Time per run', 'Relative time per run'],
+        colalign=('left', 'right', 'right', 'right'),
+        tablefmt='rst')
+    # yapf: enable
+
+    print(table_str)
+
+
+if __name__ == "__main__":
+    print("Benchmarking postcondition:")
+    print()
+    measure_functions()
diff --git a/benchmarks/against_dpcontracts/compare_precondition.py b/benchmarks/against_dpcontracts/compare_precondition.py
new file mode 100644
index 0000000..c197f4d
--- /dev/null
+++ b/benchmarks/against_dpcontracts/compare_precondition.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+
+"""
+Benchmark icontract against dpcontracts and no contracts.
+
+The benchmark was supplied by: https://github.com/Parquery/icontract/issues/142
+"""
+
+import math
+import timeit
+from typing import List
+
+import tabulate
+
+import icontract
+import dpcontracts
+
+
+@icontract.require(lambda someArg: someArg > 0)
+def function_with_icontract(someArg: int) -> float:
+    return math.sqrt(someArg)
+
+
+@dpcontracts.require("some dummy contract", lambda args: args.someArg > 0)
+def function_with_dpcontracts(someArg: int) -> float:
+    return math.sqrt(someArg)
+
+
+def function_with_inline_contract(someArg: int) -> float:
+    assert (someArg > 0)
+    return math.sqrt(someArg)
+
+
+def function_without_contracts(someArg: int) -> float:
+    return math.sqrt(someArg)
+
+
+def measure_functions() -> None:
+    funcs = [
+        'function_with_icontract',
+        'function_with_dpcontracts',
+        'function_with_inline_contract'
+    ]
+
+    durations = [0.0] * len(funcs)
+
+    number = 10 * 1000
+
+    for i, func in enumerate(funcs):
+        duration = timeit.timeit(
+            "{}(198.4)".format(func),
+            setup="from __main__ import {}".format(func),
+            number=number)
+        durations[i] = duration
+
+    table = []  # type: List[List[str]]
+
+    for func, duration in zip(funcs, durations):
+        # yapf: disable
+        table.append([
+            '`{}`'.format(func),
+            '{:.2f} s'.format(duration),
+            '{:.2f} μs'.format(duration * 1000 * 1000 / number),
+            '{:.0f}%'.format(duration * 100 / durations[1])
+        ])
+        # yapf: enable
+
+    # yapf: disable
+    table_str = tabulate.tabulate(
+        table,
+        headers=['Case', 'Total time', 'Time per run', 'Relative time per run'],
+        colalign=('left', 'right', 'right', 'right'),
+        tablefmt='rst')
+    # yapf: enable
+
+    print(table_str)
+
+
+if __name__ == "__main__":
+    print("Benchmarking precondition:")
+    print()
+    measure_functions()
diff --git a/benchmarks/benchmark_2.0.7.rst b/benchmarks/benchmark_2.0.7.rst
new file mode 100644
index 0000000..07568d2
--- /dev/null
+++ b/benchmarks/benchmark_2.0.7.rst
@@ -0,0 +1,170 @@
+Benchmarks
+==========
+We evaluated the computational costs incurred by using icontract in a couple of experiments. There are basically two
+types of costs caused by icontract: 1) extra parsing performed when you import the module regardless whether conditions
+are switched on or off and 2) run-time cost of verifying the contract.
+
+We assumed in all the experiments that the contract breaches are exceptionally seldom and thus need not to be analyzed.
+If you are interested in these additional experiments, please do let us know and create an issue on the github page.
+
+The source code of the benchmarks is available in
+`this directory <https://github.com/Parquery/icontract/tree/master/benchmarks>`_.
+All experiments were performed with Python 3.5.2+ on Intel(R) Core(TM) i7-4700MQ CPU @ 2.40GHz with 8 cores and
+4 GB RAM.
+
+We execute a short timeit snippet to bring the following measurements into context:
+
+.. code-block:: bash
+
+    python3 -m timeit -s 'import math' 'math.sqrt(1984.1234)'
+    10000000 loops, best of 3: 0.0679 usec per loop
+
+Important Note
+--------------
+The experiments were executed with icontract 2.0.7. While it would have been great to have benchmarks
+re-executed on each new version, that would require substantial effort on our side.
+Please run the scripts manually on your machine with the latest icontract version
+to obtain the current benchmark results.
+
+Import Cost
+-----------
+**Pre and post-conditions.**
+We generated a module with 100 functions. For each of these functions, we introduced a variable number of conditions and
+stop-watched how long it takes to import the module over 10 runs. Finally, we compared the import time of the module
+with contracts against the same module without any contracts in the code.
+
+The overhead is computed as the time difference between the total import time compared to the total import time of the
+module without the contracts.
+
++-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
+| Number of conditions per function | Total import time [milliseconds] | Overhead [milliseconds] | Overhead per condition and function [milliseconds] |
++===================================+==================================+=========================+====================================================+
+| None                              |                   795.59 ± 10.47 |                     N/A |                                                N/A |
++-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
+| 1                                 |                   919.53 ± 61.22 |                  123.93 |                                               1.24 |
++-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
+| 5                                 |                  1075.81 ± 59.87 |                  280.22 |                                               0.56 |
++-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
+| 10                                |                  1290.22 ± 90.04 |                  494.63 |                                               0.49 |
++-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
+| 1 disabled                        |                   833.60 ± 32.07 |                   37.41 |                                               0.37 |
++-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
+| 5 disabled                        |                   851.31 ± 66.93 |                   55.72 |                                               0.11 |
++-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
+| 10 disabled                       |                  897.90 ± 143.02 |                  101.41 |                                               0.10 |
++-----------------------------------+----------------------------------+-------------------------+----------------------------------------------------+
+
+As we can see in the table above, the overhead per condition is quite minimal (1.24 milliseconds in case of a single
+enabled contract). The overhead decreases as you add more conditions since the icontract has already initialized all the
+necessary fields in the function objects.
+
+When you disable the conditions, there is much less overhead (only 0.37 milliseconds, and decreasing). Since icontract
+returns immediately when the condition is disabled by implementation, we assume that the overhead coming from disabled
+conditions is mainly caused by Python interpreter parsing the code.
+
+
+**Invariants**. Analogously, to measure the overhead of the invariants, we generated a module with 100 classes.
+We varied the number of invariant conditions per class and measure the import time over 10 runs. The results
+are presented in the following table.
+
++--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
+| Number of conditions per class | Total import time [milliseconds] | Overhead [milliseconds] | Overhead per condition and class [milliseconds] |
++================================+==================================+=========================+=================================================+
+| None                           |                   843.61 ± 28.21 |                     N/A |                                             N/A |
++--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
+| 1                              |                  3409.71 ± 95.78 |                  2566.1 |                                           25.66 |
++--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
+| 5                              |                 4005.93 ± 131.97 |                 3162.32 |                                            6.32 |
++--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
+| 10                             |                 4801.82 ± 157.56 |                 3958.21 |                                            3.96 |
++--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
+| 1 disabled                     |                   885.88 ± 44.24 |                   42.27 |                                            0.42 |
++--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
+| 5 disabled                     |                  912.53 ± 101.91 |                   68.92 |                                            0.14 |
++--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
+| 10 disabled                    |                  963.77 ± 161.76 |                  120.16 |                                            0.12 |
++--------------------------------+----------------------------------+-------------------------+-------------------------------------------------+
+
+Similar to pre and post-conditions, there is decreasing cost per condition as number of conditions increases since the
+icontract has all set up the fields in the class metadata. However, invariants are much more costly compared to pre and
+postconditions and their overhead should be considered if import time needs to be kept minimal.
+
+The overhead of the disabled invariants is attributed to the overhead of parsing the source file as icontract
+immediately returns from disabled invariants by implementation.
+
+Run-time Cost
+-------------
+We constructed four modules around a function which computes a square root. We used ``timeit`` module to measure the
+performance. Each module was imported as part of the setup.
+
+**No precondition**. This module does not check any preconditions and serves as a baseline.
+
+.. code-block: python
+
+    import math
+    def my_sqrt(x: float) -> float:
+        return math.sqrt(x)
+
+**Pre-condition as assert**. We check pre-conditions by an assert statement.
+
+.. code-block:: python
+
+    def my_sqrt(x: float) -> float:
+        assert x >= 0
+        return math.sqrt(x)
+
+**Pre-condition as separate function**. To mimick a more complex pre-condition, we encapsulate the assert in a separate
+function.
+
+.. code-block:: python
+
+    import math
+
+    def check_non_negative(x: float) -> None:
+        assert x >= 0
+
+    def my_sqrt(x: float) -> float:
+        check_non_negative(x)
+        return math.sqrt(x)
+
+**Pre-condition with icontract**. Finally, we compare against a module that uses icontract.
+
+.. code-block:: python
+
+    import math
+    import icontract
+
+    @icontract.require(lambda x: x >= 0)
+    def my_sqrt(x: float) -> float:
+        return math.sqrt(x)
+
+**Pre-condition with icontract, disabled**. We also perform a redundant check to verify that a disabled condition
+does not incur any run-time cost.
+
+The following table sumarizes the timeit results (10000000 loops, best of 3). The run time of the baseline is computed
+as:
+
+.. code-block: bash
+
+    $ python3 -m timeit -s 'import my_sqrt' 'my_sqrt.my_sqrt(1984.1234)'
+
+The run time of the other functions is computed analogously.
+
++---------------------+-------------------------+
+| Precondition        | Run time [microseconds] |
++=====================+=========================+
+| None                |                   0.168 |
++---------------------+-------------------------+
+| As assert           |                   0.203 |
++---------------------+-------------------------+
+| As function         |                   0.274 |
++---------------------+-------------------------+
+| icontract           |                    2.78 |
++---------------------+-------------------------+
+| icontract, disabled |                   0.165 |
++---------------------+-------------------------+
+
+The overhead of icontract is substantial. While this may be prohibitive for points where computational efficiency is
+more important than correctness, mind that the overhead is still in order of microseconds. In most practical scenarios,
+where a function is more complex and takes longer than a few microseconds to execute, such a tiny overhead is
+justified by the gains in correctness, development and maintenance time.
diff --git a/icontract/__init__.py b/icontract/__init__.py
index d4a932f..905b7c2 100644
--- a/icontract/__init__.py
+++ b/icontract/__init__.py
@@ -1,4 +1,11 @@
 """Decorate functions with contracts."""
+import icontract_meta
+
+__version__ = icontract_meta.__version__
+__author__ = icontract_meta.__author__
+__copyright__ = icontract_meta.__copyright__
+__license__ = icontract_meta.__license__
+__status__ = icontract_meta.__status__
 
 # pylint: disable=invalid-name
 # pylint: disable=protected-access
diff --git a/icontract/_checkers.py b/icontract/_checkers.py
index 27bc574..d499064 100644
--- a/icontract/_checkers.py
+++ b/icontract/_checkers.py
@@ -1,9 +1,9 @@
 """Provide functions to add/find contract checkers."""
-import contextlib
 import functools
 import inspect
 import threading
-from typing import Callable, Any, Iterable, Optional, Tuple, List, Mapping, MutableMapping, Dict, Set
+from typing import Callable, Any, Iterable, Optional, Tuple, List, Mapping, \
+    MutableMapping, Dict
 
 import icontract._represent
 from icontract._globals import CallableT
@@ -52,16 +52,16 @@ def _kwargs_from_call(param_names: List[str], kwdefaults: Dict[str, Any], args:
     :return: resolved arguments as they would be passed to the function
     """
     # pylint: disable=too-many-arguments
-    mapping = dict()  # type: MutableMapping[str, Any]
+    resolved_kwargs = dict()  # type: MutableMapping[str, Any]
 
     # Set the default argument values as condition parameters.
     for param_name, param_value in kwdefaults.items():
-        mapping[param_name] = param_value
+        resolved_kwargs[param_name] = param_value
 
-    # Override the defaults with the values actually suplied to the function.
+    # Override the defaults with the values actually supplied to the function.
     for i, func_arg in enumerate(args):
         if i < len(param_names):
-            mapping[param_names[i]] = func_arg
+            resolved_kwargs[param_names[i]] = func_arg
         else:
             # Silently ignore call arguments that were not specified in the function.
             # This way we let the underlying decorated function raise the exception
@@ -69,9 +69,9 @@ def _kwargs_from_call(param_names: List[str], kwdefaults: Dict[str, Any], args:
             pass
 
     for key, val in kwargs.items():
-        mapping[key] = val
+        resolved_kwargs[key] = val
 
-    return mapping
+    return resolved_kwargs
 
 
 def _not_check(check: Any, contract: Contract) -> bool:
@@ -241,6 +241,9 @@ def _assert_postcondition(contract: Contract, resolved_kwargs: Mapping[str, Any]
             ("The argument(s) of the postcondition have not been set: {}. "
              "Does the original function define them? Did you supply them in the call?").format(missing_args))
 
+        if 'OLD' in missing_args:
+            msg_parts.append(' Did you decorate the function with a snapshot to capture OLD values?')
+
         raise TypeError(''.join(msg_parts))
 
     condition_kwargs = {
@@ -333,18 +336,12 @@ def decorate_with_checker(func: CallableT) -> CallableT:
 
     id_func = str(id(func))
 
-    def unset_checking_in_progress() -> None:
-        """Mark that the checking of the contract is finished."""
-        if hasattr(_IN_PROGRESS, id_func):
-            delattr(_IN_PROGRESS, id_func)
-
     def wrapper(*args, **kwargs):  # type: ignore
         """Wrap func by checking the preconditions and postconditions."""
         # pylint: disable=too-many-branches
 
-        with contextlib.ExitStack() as exit_stack:
-            exit_stack.callback(unset_checking_in_progress)  # pylint: disable=no-member
-
+        # Use try-finally instead of ExitStack for performance.
+        try:
             # If the wrapper is already checking the contracts for the wrapped function, avoid a recursive loop
             # by skipping any subsequent contract checks for the same function.
             if hasattr(_IN_PROGRESS, id_func):
@@ -382,7 +379,7 @@ def decorate_with_checker(func: CallableT) -> CallableT:
                 raise violation_err  # pylint: disable=raising-bad-type
 
             # Capture the snapshots
-            if postconditions:
+            if postconditions and snapshots:
                 old_as_mapping = dict()  # type: MutableMapping[str, Any]
                 for snap in snapshots:
                     # This assert is just a last defense.
@@ -406,6 +403,9 @@ def decorate_with_checker(func: CallableT) -> CallableT:
                     _assert_postcondition(contract=contract, resolved_kwargs=resolved_kwargs)
 
             return result
+        finally:
+            if hasattr(_IN_PROGRESS, id_func):
+                delattr(_IN_PROGRESS, id_func)
 
     # Copy __doc__ and other properties so that doctests can run
     functools.update_wrapper(wrapper=wrapper, wrapped=func)
@@ -465,18 +465,15 @@ def _decorate_with_invariants(func: CallableT, is_init: bool) -> CallableT:
             id_instance = str(id(instance))
             setattr(_IN_PROGRESS, id_instance, True)
 
-            def remove_from_in_progress() -> None:
-                """Remove the flag which signals that an invariant is already being checked down the call stack."""
-                if hasattr(_IN_PROGRESS, id_instance):
-                    delattr(_IN_PROGRESS, id_instance)
-
-            with contextlib.ExitStack() as exit_stack:
-                exit_stack.callback(remove_from_in_progress)  # pylint: disable=no-member
-
+            # ExitStack is not used here due to performance.
+            try:
                 for contract in instance.__class__.__invariants__:
                     _assert_invariant(contract=contract, instance=instance)
 
                 return result
+            finally:
+                if hasattr(_IN_PROGRESS, id_instance):
+                    delattr(_IN_PROGRESS, id_instance)
 
     else:
 
@@ -495,16 +492,8 @@ def _decorate_with_invariants(func: CallableT, is_init: bool) -> CallableT:
                 # Do not check any invariants to avoid endless recursion.
                 return func(*args, **kwargs)
 
-            def remove_from_in_progress() -> None:
-                """Remove the flag which signals that an invariant is already being checked down the call stack."""
-                if hasattr(_IN_PROGRESS, id_instance):
-                    delattr(_IN_PROGRESS, id_instance)
-
-            with contextlib.ExitStack() as exit_stack:
-                exit_stack.callback(remove_from_in_progress)  # pylint: disable=no-member
-
-                instance = _find_self(param_names=param_names, args=args, kwargs=kwargs)
-
+            # ExitStack is not used here due to performance.
+            try:
                 for contract in instance.__class__.__invariants__:
                     _assert_invariant(contract=contract, instance=instance)
 
@@ -514,6 +503,9 @@ def _decorate_with_invariants(func: CallableT, is_init: bool) -> CallableT:
                     _assert_invariant(contract=contract, instance=instance)
 
                 return result
+            finally:
+                if hasattr(_IN_PROGRESS, id_instance):
+                    delattr(_IN_PROGRESS, id_instance)
 
     functools.update_wrapper(wrapper=wrapper, wrapped=func)
 
diff --git a/icontract_meta.py b/icontract_meta.py
new file mode 100644
index 0000000..ba21f92
--- /dev/null
+++ b/icontract_meta.py
@@ -0,0 +1,12 @@
+"""Define meta information about mapry package."""
+
+__title__ = 'icontract'
+__description__ = (
+    'Provide design-by-contract with informative violation messages.')
+__url__ = 'https://github.com/Parquery/icontract'
+__version__ = '2.3.2.post1'
+__author__ = 'Marko Ristin'
+__author_email__ = 'marko.ristin@gmail.com'
+__license__ = 'MIT'
+__copyright__ = 'Copyright 2019 Parquery AG'
+__status__ = 'Production'
diff --git a/precommit.py b/precommit.py
index 09a9357..1c363d3 100755
--- a/precommit.py
+++ b/precommit.py
@@ -6,11 +6,70 @@ import pathlib
 import subprocess
 import sys
 
+import cpuinfo
+
+import icontract
+
+
+def benchmark_against_dpcontracts(repo_root: pathlib.Path, overwrite: bool) -> None:
+    """Run benchmars against dpcontracts and include them in the Readme."""
+    script_rel_paths = [
+        'benchmarks/against_dpcontracts/compare_invariant.py', 'benchmarks/against_dpcontracts/compare_precondition.py',
+        'benchmarks/against_dpcontracts/compare_postcondition.py'
+    ]
+
+    if not overwrite:
+        for i, script_rel_path in enumerate(script_rel_paths):
+            if i > 0:
+                print()
+            subprocess.check_call(['python3', str(repo_root / script_rel_path)])
+    else:
+        out = ['The following scripts were run:\n\n']
+        for script_rel_path in script_rel_paths:
+            out.append('* `{0} <https://github.com/Parquery/icontract/tree/master/{0}>`_\n'.format(script_rel_path))
+        out.append('\n')
+
+        out.append(('The benchmarks were executed on {}.\nWe used icontract {} and dpcontracts 0.6.0.\n\n').format(
+            cpuinfo.get_cpu_info()['brand'], icontract.__version__))
+
+        out.append('The following tables summarize the results.\n\n')
+        stdouts = []  # type: List[str]
+
+        for script_rel_path in script_rel_paths:
+            stdout = subprocess.check_output(['python3', str(repo_root / script_rel_path)]).decode()
+            stdouts.append(stdout)
+
+            out.append(stdout)
+            out.append('\n')
+
+        readme_path = repo_root / 'README.rst'
+        readme = readme_path.read_text()
+        marker_start = '.. Becnhmark report from precommit.py starts.'
+        marker_end = '.. Benchmark report from precommit.py ends.'
+        lines = readme.splitlines()
+
+        try:
+            index_start = lines.index(marker_start)
+        except ValueError as exc:
+            raise ValueError('Could not find the marker for the benchmarks in the {}: {}'.format(
+                readme_path, marker_start)) from exc
+
+        try:
+            index_end = lines.index(marker_end)
+        except ValueError as exc:
+            raise ValueError('Could not find the start marker for the benchmarks in the {}: {}'.format(
+                readme_path, marker_end)) from exc
+
+        assert index_start < index_end, 'Unexpected end marker before start marker for the benchmarks.'
+
+        lines = lines[:index_start + 1] + ['\n'] + (''.join(out)).splitlines() + ['\n'] + lines[index_end:]
+        readme_path.write_text('\n'.join(lines) + '\n')
+
+        print('\n\n'.join(stdouts))
+
 
 def main() -> int:
-    """"
-    Main routine
-    """
+    """"Execute main routine."""
     parser = argparse.ArgumentParser()
     parser.add_argument(
         "--overwrite",
@@ -61,6 +120,9 @@ def main() -> int:
 
     subprocess.check_call(["coverage", "report"])
 
+    print("Benchmarking against dpcontracts...")
+    benchmark_against_dpcontracts(repo_root=repo_root, overwrite=overwrite)
+
     print("Doctesting...")
     subprocess.check_call(["python3", "-m", "doctest", "README.rst"])
     for pth in (repo_root / "icontract").glob("**/*.py"):
diff --git a/setup.py b/setup.py
index bfc66f3..b5a98bb 100644
--- a/setup.py
+++ b/setup.py
@@ -8,6 +8,8 @@ import os
 
 from setuptools import setup, find_packages
 
+import icontract_meta
+
 # pylint: disable=redefined-builtin
 
 here = os.path.abspath(os.path.dirname(__file__))  # pylint: disable=invalid-name
@@ -16,13 +18,13 @@ with open(os.path.join(here, 'README.rst'), encoding='utf-8') as fid:
     long_description = fid.read()  # pylint: disable=invalid-name
 
 setup(
-    name='icontract',
-    version='2.3.2',
-    description='Provide design-by-contract with informative violation messages',
+    name=icontract_meta.__title__,
+    version=icontract_meta.__version__,
+    description=icontract_meta.__description__,
     long_description=long_description,
-    url='https://github.com/Parquery/icontract',
-    author='Marko Ristin',
-    author_email='marko@parquery.com',
+    url=icontract_meta.__url__,
+    author=icontract_meta.__author__,
+    author_email=icontract_meta.__author_email__,
     classifiers=[
         # yapf: disable
         'Development Status :: 5 - Production/Stable',
@@ -48,7 +50,10 @@ setup(
             'pydocstyle>=2.1.1,<3',
             'coverage>=4.5.1,<5',
             'docutils>=0.14,<1',
-            'pygments>=2.2.0,<3'
+            'pygments>=2.2.0,<3',
+            'dpcontracts==0.6.0',
+            'tabulate>=0.8.7,<1',
+            'py-cpuinfo>=5.0.0,<6'
             # yapf: enable
         ],
     },


Test patch:
diff --git a/tests/test_snapshot.py b/tests/test_snapshot.py
index 7109859..063eb38 100644
--- a/tests/test_snapshot.py
+++ b/tests/test_snapshot.py
@@ -116,21 +116,22 @@ class TestViolation(unittest.TestCase):
 
 
 class TestInvalid(unittest.TestCase):
-    def test_missing_old_snapshot(self) -> None:
+    def test_missing_snapshot_but_old_in_postcondition(self) -> None:
         @icontract.ensure(lambda OLD, val, lst: OLD.len_lst + 1 == len(lst))
         def some_func(lst: List[int], val: int) -> None:
             lst.append(val)
 
-        attribute_error = None  # type: Optional[AttributeError]
+        type_error = None  # type: Optional[TypeError]
         try:
             some_func([1], 2)
-        except AttributeError as err:
-            attribute_error = err
+        except TypeError as err:
+            type_error = err
 
-        self.assertIsNotNone(attribute_error)
-        self.assertEqual("The snapshot with the name 'len_lst' is not available in the OLD of a postcondition. "
-                         "Have you decorated the function with a corresponding snapshot decorator?",
-                         str(attribute_error))
+        self.assertIsNotNone(type_error)
+        self.assertEqual("The argument(s) of the postcondition have not been set: ['OLD']. "
+                         "Does the original function define them? Did you supply them in the call? "
+                         "Did you decorate the function with a snapshot to capture OLD values?",
+                         tests.error.wo_mandatory_location(str(type_error)))
 
     def test_conflicting_snapshots_with_argument_name(self) -> None:
         value_error = None  # type: Optional[ValueError]