mapbox / earcut.hpp

Fast, header-only polygon triangulation
ISC License
858 stars 133 forks source link

new object pool to remove boost dependency #29

Closed mrgreywater closed 8 years ago

mrgreywater commented 8 years ago

Tried creating it using a std::list<std::vector> before, but it would require require removing the explicitly deleted move constructors of Node as std::vector needs it. Additionally the performance dropped about 20%.

Instead it is now using malloc/free std::allocator<T> with blocks of Nodes, and performance is on par (even a little faster) than boost.

Before:

+----------------+--------------------+--------------------+
| Polygon        | earcut             | libtess2           |
+----------------+--------------------+--------------------+
| bad_hole       |      164.130 ops/s |       19.787 ops/s |
| building       |    1.819.283 ops/s |       51.033 ops/s |
| degenerate     |    3.543.121 ops/s |       89.198 ops/s |
| dude           |      108.706 ops/s |       11.556 ops/s |
| empty_square   |    2.406.061 ops/s |       75.893 ops/s |
| water_huge     |           88 ops/s |           60 ops/s |
| water_huge2    |           43 ops/s |           76 ops/s |
| water          |        1.206 ops/s |          160 ops/s |
| water2         |        1.385 ops/s |          709 ops/s |
| water3         |       38.820 ops/s |        5.360 ops/s |
| water3b        |      431.561 ops/s |       32.419 ops/s |
| water4         |        5.000 ops/s |        1.293 ops/s |
+----------------+--------------------+--------------------+

After

+----------------+--------------------+--------------------+
| Polygon        | earcut             | libtess2           |
+----------------+--------------------+--------------------+
| bad_hole       |      181.930 ops/s |       21.622 ops/s |
| building       |    2.629.994 ops/s |       52.097 ops/s |
| degenerate     |    7.521.410 ops/s |       91.755 ops/s |
| dude           |      115.840 ops/s |       11.662 ops/s |
| empty_square   |    3.895.782 ops/s |       77.674 ops/s |
| water_huge     |           88 ops/s |           59 ops/s |
| water_huge2    |           43 ops/s |           78 ops/s |
| water          |        1.211 ops/s |          155 ops/s |
| water2         |        1.388 ops/s |          717 ops/s |
| water3         |       39.624 ops/s |        5.475 ops/s |
| water3b        |      481.762 ops/s |       33.286 ops/s |
| water4         |        5.036 ops/s |        1.292 ops/s |
+----------------+--------------------+--------------------+
mrgreywater commented 8 years ago

see #28

mrgreywater commented 8 years ago

Code before was something like that:

    template <typename T>
    class BlockPool {
    public:
        BlockPool() : blockSize(0) { }
        BlockPool(std::size_t blockSize) {
            reset(blockSize);
        }
        template <typename... Args>
        T* construct(Args&&... args) {
            if (block->size() == blockSize) {
                assert(blockSize);
                blocks.emplace_back();
                block++;
                block->reserve(blockSize);
            }
            block->emplace_back(std::forward<Args>(args)...);
            return (T*)(&block->operator[](block->size()-1));
        }
        void reset(std::size_t new_size) {
            blockSize = new_size;
            blocks.clear();
            blocks.emplace_back();
            block = std::prev(blocks.end());
            block->reserve(blockSize);
        }
    private:
        typedef std::list<std::vector<T>> block_t;
        std::size_t blockSize = 0;
        block_t blocks;
        typename block_t::iterator block;
    };

But sadly it didn't perform as well as I'd hoped.

mrgreywater commented 8 years ago

One possible addition would be to reuse allocated blocks for later polygon triangulations that use the same Earcut object, but considering the api will change in a way that this is not possible anymore (see #17), it would unnecessarily increase the size of the class.

jfirebaugh commented 8 years ago

Thanks!