| 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 | |