xref: /oneTBB/examples/graph/binpack/binpack.cpp (revision b15aabb3)
1d86ed7fbStbbdev /*
2*b15aabb3Stbbdev     Copyright (c) 2005-2021 Intel Corporation
3d86ed7fbStbbdev 
4d86ed7fbStbbdev     Licensed under the Apache License, Version 2.0 (the "License");
5d86ed7fbStbbdev     you may not use this file except in compliance with the License.
6d86ed7fbStbbdev     You may obtain a copy of the License at
7d86ed7fbStbbdev 
8d86ed7fbStbbdev         http://www.apache.org/licenses/LICENSE-2.0
9d86ed7fbStbbdev 
10d86ed7fbStbbdev     Unless required by applicable law or agreed to in writing, software
11d86ed7fbStbbdev     distributed under the License is distributed on an "AS IS" BASIS,
12d86ed7fbStbbdev     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d86ed7fbStbbdev     See the License for the specific language governing permissions and
14d86ed7fbStbbdev     limitations under the License.
15d86ed7fbStbbdev */
16d86ed7fbStbbdev 
17d86ed7fbStbbdev /* Bin-packing algorithm that attempts to use minimal number of bins B of
18d86ed7fbStbbdev    size desired_bin_capacity to contain elements_num items of varying sizes. */
19d86ed7fbStbbdev 
20d86ed7fbStbbdev #include <cmath>
21d86ed7fbStbbdev 
22d86ed7fbStbbdev #include <string>
23d86ed7fbStbbdev #include <iostream>
24d86ed7fbStbbdev #include <tuple>
25d86ed7fbStbbdev #include <vector>
26d86ed7fbStbbdev #include <atomic>
27d86ed7fbStbbdev #include <algorithm>
28d86ed7fbStbbdev 
29d86ed7fbStbbdev #include "oneapi/tbb/tick_count.h"
30d86ed7fbStbbdev #include "oneapi/tbb/flow_graph.h"
31d86ed7fbStbbdev #include "oneapi/tbb/global_control.h"
32d86ed7fbStbbdev 
33d86ed7fbStbbdev #include "common/utility/utility.hpp"
34d86ed7fbStbbdev #include "common/utility/get_default_num_threads.hpp"
35d86ed7fbStbbdev 
36d86ed7fbStbbdev typedef std::size_t size_type; // to represent non-zero indices, capacities, etc.
37d86ed7fbStbbdev typedef std::size_t value_type; // the type of items we are attempting to pack into bins
38d86ed7fbStbbdev typedef std::vector<value_type> bin; // we use a simple vector to represent a bin
39d86ed7fbStbbdev // Our bin packers will be function nodes in the graph that take value_type items and
40d86ed7fbStbbdev // return a dummy value.  They will also implicitly send packed bins to the bin_buffer
41d86ed7fbStbbdev // node, and unused items back to the value_pool node:
42d86ed7fbStbbdev typedef oneapi::tbb::flow::
43d86ed7fbStbbdev     multifunction_node<value_type, std::tuple<value_type, bin>, oneapi::tbb::flow::rejecting>
44d86ed7fbStbbdev         bin_packer;
45d86ed7fbStbbdev // Items are placed into a pool that all bin packers grab from, represent by a queue_node:
46d86ed7fbStbbdev typedef oneapi::tbb::flow::queue_node<value_type> value_pool;
47d86ed7fbStbbdev // Packed bins are placed in this buffer waiting to be serially printed and/or accounted for:
48d86ed7fbStbbdev typedef oneapi::tbb::flow::buffer_node<bin> bin_buffer;
49d86ed7fbStbbdev // Packed bins are taken from the_bin_buffer and processed by the_writer:
50d86ed7fbStbbdev typedef oneapi::tbb::flow::
51d86ed7fbStbbdev     function_node<bin, oneapi::tbb::flow::continue_msg, oneapi::tbb::flow::rejecting>
52d86ed7fbStbbdev         bin_writer;
53d86ed7fbStbbdev // Items are injected into the graph when this node sends them to the_value_pool:
54d86ed7fbStbbdev typedef oneapi::tbb::flow::input_node<value_type> value_source;
55d86ed7fbStbbdev 
56d86ed7fbStbbdev // User-specified globals with default values
57d86ed7fbStbbdev size_type desired_bin_capacity = 42;
58d86ed7fbStbbdev size_type elements_num = 1000; // number of elements to generate
59d86ed7fbStbbdev bool verbose = false; // prints bin details and other diagnostics to screen
60d86ed7fbStbbdev bool silent = false; // suppress all output except for time
61d86ed7fbStbbdev int num_bin_packers = -1; // number of concurrent bin packers in operation; default is #threads;
62d86ed7fbStbbdev     // larger values can result in more bins at less than full capacity
63d86ed7fbStbbdev size_type optimality =
64d86ed7fbStbbdev     1; // 1 (default) is highest the algorithm can obtain; larger numbers run faster
65d86ed7fbStbbdev 
66d86ed7fbStbbdev // Calculated globals
67d86ed7fbStbbdev size_type bins_num_min; // lower bound on the optimal number of bins
68d86ed7fbStbbdev size_type bins_num; // the answer, i.e. number of bins used by the algorithm
69d86ed7fbStbbdev std::vector<size_type> input_array; // stores randomly generated input values
70d86ed7fbStbbdev value_type item_sum; // sum of all randomly generated input values
71d86ed7fbStbbdev std::atomic<value_type> packed_sum; // sum of all values currently packed into all bins
72d86ed7fbStbbdev std::atomic<size_type> packed_items; // number of values currently packed into all bins
73d86ed7fbStbbdev std::atomic<size_type> active_bins; // number of active bin_packers
74d86ed7fbStbbdev std::vector<bin_packer*> bins; // the array of bin packers
75d86ed7fbStbbdev 
76d86ed7fbStbbdev // This class is the Body type for bin_packer
77d86ed7fbStbbdev class bin_filler {
78d86ed7fbStbbdev     typedef bin_packer::output_ports_type ports_type;
79d86ed7fbStbbdev     bin my_bin; // the current bin that this bin_filler is packing
80d86ed7fbStbbdev     size_type
81d86ed7fbStbbdev         my_used; // capacity of bin used by current contents (not to be confused with my_bin.size())
82d86ed7fbStbbdev     size_type relax,
83d86ed7fbStbbdev         relax_val; // relaxation counter for determining when to settle for a non-full bin
84d86ed7fbStbbdev     bin_packer* my_bin_packer; // ptr to the bin packer that this body object is associated with
85d86ed7fbStbbdev     size_type bin_index; // index of the encapsulating bin packer in the global bins array
86d86ed7fbStbbdev     value_type looking_for; // the minimum size of item this bin_packer will accept
87d86ed7fbStbbdev     value_pool* the_value_pool; // the queue of incoming values
88d86ed7fbStbbdev     bool done; // flag to indicate that this binpacker has been deactivated
89d86ed7fbStbbdev public:
bin_filler(std::size_t bidx,value_pool * _q)90d86ed7fbStbbdev     bin_filler(std::size_t bidx, value_pool* _q)
91d86ed7fbStbbdev             : my_used(0),
92d86ed7fbStbbdev               relax(0),
93d86ed7fbStbbdev               relax_val(0),
94d86ed7fbStbbdev               my_bin_packer(nullptr),
95d86ed7fbStbbdev               bin_index(bidx),
96d86ed7fbStbbdev               looking_for(desired_bin_capacity),
97d86ed7fbStbbdev               the_value_pool(_q),
98d86ed7fbStbbdev               done(false) {}
operator ()(const value_type & item,ports_type & p)99d86ed7fbStbbdev     void operator()(const value_type& item, ports_type& p) {
100d86ed7fbStbbdev         if (!my_bin_packer)
101d86ed7fbStbbdev             my_bin_packer = bins[bin_index];
102d86ed7fbStbbdev         if (done)
103d86ed7fbStbbdev             // this bin_packer is done packing items; put item back to pool
104d86ed7fbStbbdev             std::get<0>(p).try_put(item);
105d86ed7fbStbbdev         else if (
106d86ed7fbStbbdev             item >
107d86ed7fbStbbdev             desired_bin_capacity) { // signal that packed_sum has reached item_sum at some point
108d86ed7fbStbbdev             size_type remaining = active_bins--;
109d86ed7fbStbbdev             if (remaining == 1 &&
110d86ed7fbStbbdev                 packed_sum == item_sum) { // this is the last bin and it has seen everything
111d86ed7fbStbbdev                 // this bin_packer may not have seen everything, so stay active
112d86ed7fbStbbdev                 if (my_used > 0)
113d86ed7fbStbbdev                     std::get<1>(p).try_put(my_bin);
114d86ed7fbStbbdev                 my_bin.clear();
115d86ed7fbStbbdev                 my_used = 0;
116d86ed7fbStbbdev                 looking_for = desired_bin_capacity;
117d86ed7fbStbbdev                 ++active_bins;
118d86ed7fbStbbdev             }
119d86ed7fbStbbdev             else if (remaining == 1) { // this is the last bin, but there are remaining items
120d86ed7fbStbbdev                 std::get<0>(p).try_put(desired_bin_capacity + 1); // send out signal
121d86ed7fbStbbdev                 ++active_bins;
122d86ed7fbStbbdev             }
123d86ed7fbStbbdev             else if (remaining > 1) { // this is not the last bin; deactivate
124d86ed7fbStbbdev                 // this bin is ill-utilized; throw back items and deactivate
125d86ed7fbStbbdev                 if (my_used < desired_bin_capacity / (1 + optimality * .1)) {
126d86ed7fbStbbdev                     packed_sum -= my_used;
127d86ed7fbStbbdev                     packed_items -= my_bin.size();
128d86ed7fbStbbdev                     for (size_type i = 0; i < my_bin.size(); ++i)
129d86ed7fbStbbdev                         std::get<0>(p).try_put(my_bin[i]);
130d86ed7fbStbbdev                     oneapi::tbb::flow::remove_edge(*the_value_pool, *my_bin_packer); // deactivate
131d86ed7fbStbbdev                     done = true;
132d86ed7fbStbbdev                     std::get<0>(p).try_put(desired_bin_capacity + 1); // send out signal
133d86ed7fbStbbdev                 }
134d86ed7fbStbbdev                 else { // this bin is well-utilized; send out bin and deactivate
135d86ed7fbStbbdev                     oneapi::tbb::flow::remove_edge(*the_value_pool,
136d86ed7fbStbbdev                                                    *my_bin_packer); // build no more bins
137d86ed7fbStbbdev                     done = true;
138d86ed7fbStbbdev                     if (my_used > 0)
139d86ed7fbStbbdev                         std::get<1>(p).try_put(my_bin);
140d86ed7fbStbbdev                     std::get<0>(p).try_put(desired_bin_capacity + 1); // send out signal
141d86ed7fbStbbdev                 }
142d86ed7fbStbbdev             }
143d86ed7fbStbbdev         }
144d86ed7fbStbbdev         else if (item <= desired_bin_capacity - my_used &&
145d86ed7fbStbbdev                  item >= looking_for) { // this item can be packed
146d86ed7fbStbbdev             my_bin.push_back(item);
147d86ed7fbStbbdev             my_used += item;
148d86ed7fbStbbdev             packed_sum += item;
149d86ed7fbStbbdev             ++packed_items;
150d86ed7fbStbbdev             looking_for = desired_bin_capacity - my_used;
151d86ed7fbStbbdev             relax = 0;
152d86ed7fbStbbdev             if (packed_sum == item_sum) {
153d86ed7fbStbbdev                 std::get<0>(p).try_put(desired_bin_capacity + 1); // send out signal
154d86ed7fbStbbdev             }
155d86ed7fbStbbdev             if (my_used == desired_bin_capacity) {
156d86ed7fbStbbdev                 std::get<1>(p).try_put(my_bin);
157d86ed7fbStbbdev                 my_bin.clear();
158d86ed7fbStbbdev                 my_used = 0;
159d86ed7fbStbbdev                 looking_for = desired_bin_capacity;
160d86ed7fbStbbdev             }
161d86ed7fbStbbdev         }
162d86ed7fbStbbdev         else { // this item can't be packed; relax constraints
163d86ed7fbStbbdev             ++relax;
164d86ed7fbStbbdev             // this bin_packer has looked through enough items
165d86ed7fbStbbdev             if (relax >= (elements_num - packed_items) / optimality) {
166d86ed7fbStbbdev                 relax = 0;
167d86ed7fbStbbdev                 --looking_for; // accept a wider range of items
168d86ed7fbStbbdev                 if (looking_for == 0 && my_used < desired_bin_capacity / (1 + optimality * .1) &&
169d86ed7fbStbbdev                     my_used > 0 && active_bins > 1) {
170d86ed7fbStbbdev                     // this bin_packer is ill-utilized and can't find items; deactivate and throw back items
171d86ed7fbStbbdev                     size_type remaining = active_bins--;
172d86ed7fbStbbdev                     if (remaining > 1) { // not the last bin_packer
173d86ed7fbStbbdev                         oneapi::tbb::flow::remove_edge(*the_value_pool,
174d86ed7fbStbbdev                                                        *my_bin_packer); // deactivate
175d86ed7fbStbbdev                         done = true;
176d86ed7fbStbbdev                     }
177d86ed7fbStbbdev                     else
178d86ed7fbStbbdev                         active_bins++; // can't deactivate last bin_packer
179d86ed7fbStbbdev                     packed_sum -= my_used;
180d86ed7fbStbbdev                     packed_items -= my_bin.size();
181d86ed7fbStbbdev                     for (size_type i = 0; i < my_bin.size(); ++i)
182d86ed7fbStbbdev                         std::get<0>(p).try_put(my_bin[i]);
183d86ed7fbStbbdev                     my_bin.clear();
184d86ed7fbStbbdev                     my_used = 0;
185d86ed7fbStbbdev                 }
186d86ed7fbStbbdev                 else if (looking_for == 0 &&
187d86ed7fbStbbdev                          (my_used >= desired_bin_capacity / (1 + optimality * .1) ||
188d86ed7fbStbbdev                           active_bins == 1)) {
189d86ed7fbStbbdev                     // this bin_packer can't find items but is well-utilized, so send it out and reset
190d86ed7fbStbbdev                     std::get<1>(p).try_put(my_bin);
191d86ed7fbStbbdev                     my_bin.clear();
192d86ed7fbStbbdev                     my_used = 0;
193d86ed7fbStbbdev                     looking_for = desired_bin_capacity;
194d86ed7fbStbbdev                 }
195d86ed7fbStbbdev             }
196d86ed7fbStbbdev             std::get<0>(p).try_put(item); // put unused item back to pool
197d86ed7fbStbbdev         }
198d86ed7fbStbbdev     }
199d86ed7fbStbbdev };
200d86ed7fbStbbdev 
201d86ed7fbStbbdev // input node uses this to send the values to the value_pool
202d86ed7fbStbbdev class item_generator {
203d86ed7fbStbbdev     size_type counter;
204d86ed7fbStbbdev 
205d86ed7fbStbbdev public:
item_generator()206d86ed7fbStbbdev     item_generator() : counter(0) {}
operator ()(oneapi::tbb::flow_control & fc)207d86ed7fbStbbdev     value_type operator()(oneapi::tbb::flow_control& fc) {
208d86ed7fbStbbdev         if (counter < elements_num) {
209d86ed7fbStbbdev             value_type result = input_array[counter];
210d86ed7fbStbbdev             ++counter;
211d86ed7fbStbbdev             return result;
212d86ed7fbStbbdev         }
213d86ed7fbStbbdev 
214d86ed7fbStbbdev         fc.stop();
215d86ed7fbStbbdev         return value_type{};
216d86ed7fbStbbdev     }
217d86ed7fbStbbdev };
218d86ed7fbStbbdev 
219d86ed7fbStbbdev // the terminal function_node uses this to gather stats and print bin information
220d86ed7fbStbbdev class bin_printer {
221d86ed7fbStbbdev     value_type running_count;
222d86ed7fbStbbdev     size_type item_count;
223d86ed7fbStbbdev     value_type my_min, my_max;
224d86ed7fbStbbdev     double avg;
225d86ed7fbStbbdev 
226d86ed7fbStbbdev public:
bin_printer()227d86ed7fbStbbdev     bin_printer()
228d86ed7fbStbbdev             : running_count(0),
229d86ed7fbStbbdev               item_count(0),
230d86ed7fbStbbdev               my_min(desired_bin_capacity),
231d86ed7fbStbbdev               my_max(0),
232d86ed7fbStbbdev               avg(0) {}
operator ()(bin b)233d86ed7fbStbbdev     oneapi::tbb::flow::continue_msg operator()(bin b) {
234d86ed7fbStbbdev         value_type sum = 0;
235d86ed7fbStbbdev         ++bins_num;
236d86ed7fbStbbdev         if (verbose)
237d86ed7fbStbbdev             std::cout << "[ ";
238d86ed7fbStbbdev         for (size_type i = 0; i < b.size(); ++i) {
239d86ed7fbStbbdev             if (verbose)
240d86ed7fbStbbdev                 std::cout << b[i] << " ";
241d86ed7fbStbbdev             sum += b[i];
242d86ed7fbStbbdev             ++item_count;
243d86ed7fbStbbdev         }
244d86ed7fbStbbdev         my_min = std::min(sum, my_min);
245d86ed7fbStbbdev         my_max = std::max(sum, my_max);
246d86ed7fbStbbdev         avg += sum;
247d86ed7fbStbbdev         running_count += sum;
248d86ed7fbStbbdev         if (verbose) {
249d86ed7fbStbbdev             std::cout << "]=" << sum << "; Done/Packed/Total cap: " << running_count << "/"
250d86ed7fbStbbdev                       << packed_sum << "/" << item_sum << " items:" << item_count << "/"
251d86ed7fbStbbdev                       << packed_items << "/" << elements_num << " bins_num=" << bins_num << "\n";
252d86ed7fbStbbdev         }
253d86ed7fbStbbdev         if (item_count == elements_num) { // should be the last; print stats
254d86ed7fbStbbdev             avg = avg / (double)bins_num;
255d86ed7fbStbbdev             if (!silent)
256d86ed7fbStbbdev                 std::cout << "SUMMARY: #Bins used: " << bins_num << "; Avg size: " << avg
257d86ed7fbStbbdev                           << "; Max size: " << my_max << "; Min size: " << my_min << "\n"
258d86ed7fbStbbdev                           << "         Lower bound on optimal #bins: " << bins_num_min
259d86ed7fbStbbdev                           << "; Start #bins: " << num_bin_packers << "\n";
260d86ed7fbStbbdev         }
261d86ed7fbStbbdev         return oneapi::tbb::flow::continue_msg(); // need to return something
262d86ed7fbStbbdev     }
263d86ed7fbStbbdev };
264d86ed7fbStbbdev 
main(int argc,char * argv[])265d86ed7fbStbbdev int main(int argc, char* argv[]) {
266d86ed7fbStbbdev     utility::thread_number_range threads(utility::get_default_num_threads);
267d86ed7fbStbbdev     utility::parse_cli_arguments(
268d86ed7fbStbbdev         argc,
269d86ed7fbStbbdev         argv,
270d86ed7fbStbbdev         utility::cli_argument_pack()
271d86ed7fbStbbdev             //"-h" option for displaying help is present implicitly
272d86ed7fbStbbdev             .positional_arg(threads, "#threads", utility::thread_number_range_desc)
273d86ed7fbStbbdev             .arg(verbose, "verbose", "   print diagnostic output to screen")
274d86ed7fbStbbdev             .arg(silent, "silent", "    limits output to timing info; overrides verbose")
275d86ed7fbStbbdev             .arg(elements_num, "elements_num", "         number of values to pack")
276d86ed7fbStbbdev             .arg(desired_bin_capacity, "bin_capacity", "         capacity of each bin")
277d86ed7fbStbbdev             .arg(num_bin_packers,
278d86ed7fbStbbdev                  "#packers",
279d86ed7fbStbbdev                  "  number of concurrent bin packers to use "
280d86ed7fbStbbdev                  "(default=#threads)")
281d86ed7fbStbbdev             .arg(optimality,
282d86ed7fbStbbdev                  "optimality",
283d86ed7fbStbbdev                  "controls optimality of solution; 1 is highest, use\n"
284d86ed7fbStbbdev                  "              larger numbers for less optimal but faster solution"));
285d86ed7fbStbbdev 
286d86ed7fbStbbdev     if (silent)
287d86ed7fbStbbdev         verbose = false; // make silent override verbose
288d86ed7fbStbbdev     // Generate random input data
289d86ed7fbStbbdev     srand(42);
290d86ed7fbStbbdev     input_array.resize(elements_num);
291d86ed7fbStbbdev     item_sum = 0;
292d86ed7fbStbbdev     for (auto& item : input_array) {
293d86ed7fbStbbdev         item = rand() % desired_bin_capacity + 1; // generate items that fit in a bin
294d86ed7fbStbbdev         item_sum += item;
295d86ed7fbStbbdev     }
296d86ed7fbStbbdev     bins_num_min = (item_sum % desired_bin_capacity) ? item_sum / desired_bin_capacity + 1
297d86ed7fbStbbdev                                                      : item_sum / desired_bin_capacity;
298d86ed7fbStbbdev 
299d86ed7fbStbbdev     oneapi::tbb::tick_count start = oneapi::tbb::tick_count::now();
300d86ed7fbStbbdev     for (int p = threads.first; p <= threads.last; p = threads.step(p)) {
301d86ed7fbStbbdev         oneapi::tbb::global_control c(oneapi::tbb::global_control::max_allowed_parallelism, p);
302d86ed7fbStbbdev         packed_sum = 0;
303d86ed7fbStbbdev         packed_items = 0;
304d86ed7fbStbbdev         bins_num = 0;
305d86ed7fbStbbdev         if (num_bin_packers == -1)
306d86ed7fbStbbdev             num_bin_packers = p;
307d86ed7fbStbbdev         active_bins = num_bin_packers;
308d86ed7fbStbbdev         if (!silent)
309d86ed7fbStbbdev             std::cout << "binpack running with " << item_sum << " capacity over " << elements_num
310d86ed7fbStbbdev                       << " items, optimality=" << optimality << ", " << num_bin_packers
311d86ed7fbStbbdev                       << " bins of capacity=" << desired_bin_capacity << " on " << p << " threads."
312d86ed7fbStbbdev                       << "\n";
313d86ed7fbStbbdev         oneapi::tbb::flow::graph g;
314d86ed7fbStbbdev         value_source the_source(g, item_generator());
315d86ed7fbStbbdev         value_pool the_value_pool(g);
316d86ed7fbStbbdev         oneapi::tbb::flow::make_edge(the_source, the_value_pool);
317d86ed7fbStbbdev         bin_buffer the_bin_buffer(g);
318d86ed7fbStbbdev         bins.resize(num_bin_packers);
319d86ed7fbStbbdev         for (int i = 0; i < num_bin_packers; ++i) {
320d86ed7fbStbbdev             bins[i] = new bin_packer(g, 1, bin_filler(i, &the_value_pool));
321d86ed7fbStbbdev             oneapi::tbb::flow::make_edge(the_value_pool, *(bins[i]));
322d86ed7fbStbbdev             oneapi::tbb::flow::make_edge(oneapi::tbb::flow::output_port<0>(*(bins[i])),
323d86ed7fbStbbdev                                          the_value_pool);
324d86ed7fbStbbdev             oneapi::tbb::flow::make_edge(oneapi::tbb::flow::output_port<1>(*(bins[i])),
325d86ed7fbStbbdev                                          the_bin_buffer);
326d86ed7fbStbbdev         }
327d86ed7fbStbbdev         bin_writer the_writer(g, 1, bin_printer());
328d86ed7fbStbbdev         make_edge(the_bin_buffer, the_writer);
329d86ed7fbStbbdev         the_source.activate();
330d86ed7fbStbbdev         g.wait_for_all();
331d86ed7fbStbbdev         for (int i = 0; i < num_bin_packers; ++i) {
332d86ed7fbStbbdev             delete bins[i];
333d86ed7fbStbbdev         }
334d86ed7fbStbbdev     }
335d86ed7fbStbbdev     utility::report_elapsed_time((oneapi::tbb::tick_count::now() - start).seconds());
336d86ed7fbStbbdev     return 0;
337d86ed7fbStbbdev }
338