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