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