xref: /oneTBB/examples/graph/binpack/binpack.cpp (revision d86ed7fb)
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