stats_collector.d revision 1.1.1.1
1// Written in the D programming language.
2/**
3Allocator that collects useful statistics about allocations, both global and per
4calling point. The statistics collected can be configured statically by choosing
5combinations of `Options` appropriately.
6
7Example:
8----
9import std.experimental.allocator.gc_allocator : GCAllocator;
10import std.experimental.allocator.building_blocks.free_list : FreeList;
11alias Allocator = StatsCollector!(GCAllocator, Options.bytesUsed);
12----
13*/
14module std.experimental.allocator.building_blocks.stats_collector;
15
16import std.experimental.allocator.common;
17
18/**
19_Options for $(D StatsCollector) defined below. Each enables during
20compilation one specific counter, statistic, or other piece of information.
21*/
22enum Options : ulong
23{
24    /**
25    Counts the number of calls to $(D owns).
26    */
27    numOwns = 1u << 0,
28    /**
29    Counts the number of calls to $(D allocate). All calls are counted,
30    including requests for zero bytes or failed requests.
31    */
32    numAllocate = 1u << 1,
33    /**
34    Counts the number of calls to $(D allocate) that succeeded, i.e. they
35    returned a block as large as requested. (N.B. requests for zero bytes count
36    as successful.)
37    */
38    numAllocateOK = 1u << 2,
39    /**
40    Counts the number of calls to $(D expand), regardless of arguments or
41    result.
42    */
43    numExpand = 1u << 3,
44    /**
45    Counts the number of calls to $(D expand) that resulted in a successful
46    expansion.
47    */
48    numExpandOK = 1u << 4,
49    /**
50    Counts the number of calls to $(D reallocate), regardless of arguments or
51    result.
52    */
53    numReallocate = 1u << 5,
54    /**
55    Counts the number of calls to $(D reallocate) that succeeded.
56    (Reallocations to zero bytes count as successful.)
57    */
58    numReallocateOK = 1u << 6,
59    /**
60    Counts the number of calls to $(D reallocate) that resulted in an in-place
61    reallocation (no memory moved). If this number is close to the total number
62    of reallocations, that indicates the allocator finds room at the current
63    block's end in a large fraction of the cases, but also that internal
64    fragmentation may be high (the size of the unit of allocation is large
65    compared to the typical allocation size of the application).
66    */
67    numReallocateInPlace = 1u << 7,
68    /**
69    Counts the number of calls to $(D deallocate).
70    */
71    numDeallocate = 1u << 8,
72    /**
73    Counts the number of calls to $(D deallocateAll).
74    */
75    numDeallocateAll = 1u << 9,
76    /**
77    Chooses all $(D numXxx) flags.
78    */
79    numAll = (1u << 10) - 1,
80    /**
81    Tracks bytes currently allocated by this allocator. This number goes up
82    and down as memory is allocated and deallocated, and is zero if the
83    allocator currently has no active allocation.
84    */
85    bytesUsed = 1u << 10,
86    /**
87    Tracks total cumulative bytes allocated by means of $(D allocate),
88    $(D expand), and $(D reallocate) (when resulting in an expansion). This
89    number always grows and indicates allocation traffic. To compute bytes
90    deallocated cumulatively, subtract $(D bytesUsed) from $(D bytesAllocated).
91    */
92    bytesAllocated = 1u << 11,
93    /**
94    Tracks the sum of all $(D delta) values in calls of the form
95    $(D expand(b, delta)) that succeed (return $(D true)).
96    */
97    bytesExpanded = 1u << 12,
98    /**
99    Tracks the sum of all $(D b.length - s) with $(D b.length > s) in calls of
100    the form $(D realloc(b, s)) that succeed (return $(D true)). In per-call
101    statistics, also unambiguously counts the bytes deallocated with
102    $(D deallocate).
103    */
104    bytesContracted = 1u << 13,
105    /**
106    Tracks the sum of all bytes moved as a result of calls to $(D realloc) that
107    were unable to reallocate in place. A large number (relative to $(D
108    bytesAllocated)) indicates that the application should use larger
109    preallocations.
110    */
111    bytesMoved = 1u << 14,
112    /**
113    Tracks the sum of all bytes NOT moved as result of calls to $(D realloc)
114    that managed to reallocate in place. A large number (relative to $(D
115    bytesAllocated)) indicates that the application is expansion-intensive and
116    is saving a good amount of moves. However, if this number is relatively
117    small and $(D bytesSlack) is high, it means the application is
118    overallocating for little benefit.
119    */
120    bytesNotMoved = 1u << 15,
121    /**
122    Measures the sum of extra bytes allocated beyond the bytes requested, i.e.
123    the $(HTTP goo.gl/YoKffF, internal fragmentation). This is the current
124    effective number of slack bytes, and it goes up and down with time.
125    */
126    bytesSlack = 1u << 16,
127    /**
128    Measures the maximum bytes allocated over the time. This is useful for
129    dimensioning allocators.
130    */
131    bytesHighTide = 1u << 17,
132    /**
133    Chooses all $(D byteXxx) flags.
134    */
135    bytesAll = ((1u << 18) - 1) & ~numAll,
136    /**
137    Combines all flags above.
138    */
139    all = (1u << 18) - 1
140}
141
142/**
143
144Allocator that collects extra data about allocations. Since each piece of
145information adds size and time overhead, statistics can be individually enabled
146or disabled through compile-time $(D flags).
147
148All stats of the form $(D numXxx) record counts of events occurring, such as
149calls to functions and specific results. The stats of the form $(D bytesXxx)
150collect cumulative sizes.
151
152In addition, the data $(D callerSize), $(D callerModule), $(D callerFile), $(D
153callerLine), and $(D callerTime) is associated with each specific allocation.
154This data prefixes each allocation.
155
156*/
157struct StatsCollector(Allocator, ulong flags = Options.all,
158    ulong perCallFlags = 0)
159{
160private:
161    import std.traits : hasMember, Signed;
162    import std.typecons : Ternary;
163
164    static string define(string type, string[] names...)
165    {
166        string result;
167        foreach (v; names)
168            result ~= "static if (flags & Options."~v~") {"
169                ~ "private "~type~" _"~v~";"
170                ~ "public const("~type~") "~v~"() const { return _"~v~"; }"
171                ~ "}";
172        return result;
173    }
174
175    void add(string counter)(Signed!size_t n)
176    {
177        mixin("static if (flags & Options." ~ counter
178            ~ ") _" ~ counter ~ " += n;");
179        static if (counter == "bytesUsed" && (flags & Options.bytesHighTide))
180        {
181            if (bytesHighTide < bytesUsed ) _bytesHighTide = bytesUsed;
182        }
183    }
184
185    void up(string counter)() { add!counter(1); }
186    void down(string counter)() { add!counter(-1); }
187
188    version (StdDdoc)
189    {
190        /**
191        Read-only properties enabled by the homonym $(D flags) chosen by the
192        user.
193
194        Example:
195        ----
196        StatsCollector!(Mallocator,
197            Options.bytesUsed | Options.bytesAllocated) a;
198        auto d1 = a.allocate(10);
199        auto d2 = a.allocate(11);
200        a.deallocate(d1);
201        assert(a.bytesAllocated == 21);
202        assert(a.bytesUsed == 11);
203        a.deallocate(d2);
204        assert(a.bytesAllocated == 21);
205        assert(a.bytesUsed == 0);
206        ----
207        */
208        @property ulong numOwns() const;
209        /// Ditto
210        @property ulong numAllocate() const;
211        /// Ditto
212        @property ulong numAllocateOK() const;
213        /// Ditto
214        @property ulong numExpand() const;
215        /// Ditto
216        @property ulong numExpandOK() const;
217        /// Ditto
218        @property ulong numReallocate() const;
219        /// Ditto
220        @property ulong numReallocateOK() const;
221        /// Ditto
222        @property ulong numReallocateInPlace() const;
223        /// Ditto
224        @property ulong numDeallocate() const;
225        /// Ditto
226        @property ulong numDeallocateAll() const;
227        /// Ditto
228        @property ulong bytesUsed() const;
229        /// Ditto
230        @property ulong bytesAllocated() const;
231        /// Ditto
232        @property ulong bytesExpanded() const;
233        /// Ditto
234        @property ulong bytesContracted() const;
235        /// Ditto
236        @property ulong bytesMoved() const;
237        /// Ditto
238        @property ulong bytesNotMoved() const;
239        /// Ditto
240        @property ulong bytesSlack() const;
241        /// Ditto
242        @property ulong bytesHighTide() const;
243    }
244
245public:
246    /**
247    The parent allocator is publicly accessible either as a direct member if it
248    holds state, or as an alias to `Allocator.instance` otherwise. One may use
249    it for making calls that won't count toward statistics collection.
250    */
251    static if (stateSize!Allocator) Allocator parent;
252    else alias parent = Allocator.instance;
253
254private:
255    // Per-allocator state
256    mixin(define("ulong",
257        "numOwns",
258        "numAllocate",
259        "numAllocateOK",
260        "numExpand",
261        "numExpandOK",
262        "numReallocate",
263        "numReallocateOK",
264        "numReallocateInPlace",
265        "numDeallocate",
266        "numDeallocateAll",
267        "bytesUsed",
268        "bytesAllocated",
269        "bytesExpanded",
270        "bytesContracted",
271        "bytesMoved",
272        "bytesNotMoved",
273        "bytesSlack",
274        "bytesHighTide",
275    ));
276
277public:
278
279    /// Alignment offered is equal to $(D Allocator.alignment).
280    alias alignment = Allocator.alignment;
281
282    /**
283    Increments $(D numOwns) (per instance and and per call) and forwards to $(D
284    parent.owns(b)).
285    */
286    static if (hasMember!(Allocator, "owns"))
287    {
288        static if ((perCallFlags & Options.numOwns) == 0)
289        Ternary owns(void[] b)
290        { return ownsImpl(b); }
291        else
292        Ternary owns(string f = __FILE, uint n = line)(void[] b)
293        { return ownsImpl!(f, n)(b); }
294    }
295
296    private Ternary ownsImpl(string f = null, uint n = 0)(void[] b)
297    {
298        up!"numOwns";
299        addPerCall!(f, n, "numOwns")(1);
300        return parent.owns(b);
301    }
302
303    /**
304    Forwards to $(D parent.allocate). Affects per instance: $(D numAllocate),
305    $(D bytesUsed), $(D bytesAllocated), $(D bytesSlack), $(D numAllocateOK),
306    and $(D bytesHighTide). Affects per call: $(D numAllocate), $(D
307    numAllocateOK), and $(D bytesAllocated).
308    */
309    static if (!(perCallFlags
310        & (Options.numAllocate | Options.numAllocateOK
311            | Options.bytesAllocated)))
312    {
313        void[] allocate(size_t n)
314        { return allocateImpl(n); }
315    }
316    else
317    {
318        void[] allocate(string f = __FILE__, ulong n = __LINE__)
319            (size_t bytes)
320        { return allocateImpl!(f, n)(bytes); }
321    }
322
323    private void[] allocateImpl(string f = null, ulong n = 0)(size_t bytes)
324    {
325        auto result = parent.allocate(bytes);
326        add!"bytesUsed"(result.length);
327        add!"bytesAllocated"(result.length);
328        immutable slack = this.goodAllocSize(result.length) - result.length;
329        add!"bytesSlack"(slack);
330        up!"numAllocate";
331        add!"numAllocateOK"(result.length == bytes); // allocating 0 bytes is OK
332        addPerCall!(f, n, "numAllocate", "numAllocateOK", "bytesAllocated")
333            (1, result.length == bytes, result.length);
334        return result;
335    }
336
337    /**
338    Defined whether or not $(D Allocator.expand) is defined. Affects
339    per instance: $(D numExpand), $(D numExpandOK), $(D bytesExpanded),
340    $(D bytesSlack), $(D bytesAllocated), and $(D bytesUsed). Affects per call:
341    $(D numExpand), $(D numExpandOK), $(D bytesExpanded), and
342    $(D bytesAllocated).
343    */
344    static if (!(perCallFlags
345        & (Options.numExpand | Options.numExpandOK | Options.bytesExpanded)))
346    {
347        bool expand(ref void[] b, size_t delta)
348        { return expandImpl(b, delta); }
349    }
350    else
351    {
352        bool expand(string f = __FILE__, uint n = __LINE__)
353            (ref void[] b, size_t delta)
354        { return expandImpl!(f, n)(b, delta); }
355    }
356
357    private bool expandImpl(string f = null, uint n = 0)(ref void[] b, size_t s)
358    {
359        up!"numExpand";
360        Signed!size_t slack = 0;
361        static if (!hasMember!(Allocator, "expand"))
362        {
363            auto result = s == 0;
364        }
365        else
366        {
367            immutable bytesSlackB4 = this.goodAllocSize(b.length) - b.length;
368            auto result = parent.expand(b, s);
369            if (result)
370            {
371                up!"numExpandOK";
372                add!"bytesUsed"(s);
373                add!"bytesAllocated"(s);
374                add!"bytesExpanded"(s);
375                slack = Signed!size_t(this.goodAllocSize(b.length) - b.length
376                    - bytesSlackB4);
377                add!"bytesSlack"(slack);
378            }
379        }
380        immutable xtra = result ? s : 0;
381        addPerCall!(f, n, "numExpand", "numExpandOK", "bytesExpanded",
382            "bytesAllocated")
383            (1, result, xtra, xtra);
384        return result;
385    }
386
387    /**
388    Defined whether or not $(D Allocator.reallocate) is defined. Affects
389    per instance: $(D numReallocate), $(D numReallocateOK), $(D
390    numReallocateInPlace), $(D bytesNotMoved), $(D bytesAllocated), $(D
391    bytesSlack), $(D bytesExpanded), and $(D bytesContracted). Affects per call:
392    $(D numReallocate), $(D numReallocateOK), $(D numReallocateInPlace),
393    $(D bytesNotMoved), $(D bytesExpanded), $(D bytesContracted), and
394    $(D bytesMoved).
395    */
396    static if (!(perCallFlags
397        & (Options.numReallocate | Options.numReallocateOK
398            | Options.numReallocateInPlace | Options.bytesNotMoved
399            | Options.bytesExpanded | Options.bytesContracted
400            | Options.bytesMoved)))
401    {
402        bool reallocate(ref void[] b, size_t s)
403        { return reallocateImpl(b, s); }
404    }
405    else
406    {
407        bool reallocate(string f = __FILE__, ulong n = __LINE__)
408            (ref void[] b, size_t s)
409        { return reallocateImpl!(f, n)(b, s); }
410    }
411
412    private bool reallocateImpl(string f = null, uint n = 0)
413        (ref void[] b, size_t s)
414    {
415        up!"numReallocate";
416        const bytesSlackB4 = this.goodAllocSize(b.length) - b.length;
417        const oldB = b.ptr;
418        const oldLength = b.length;
419
420        const result = parent.reallocate(b, s);
421
422        Signed!size_t slack = 0;
423        bool wasInPlace = false;
424        Signed!size_t delta = 0;
425
426        if (result)
427        {
428            up!"numReallocateOK";
429            slack = (this.goodAllocSize(b.length) - b.length) - bytesSlackB4;
430            add!"bytesSlack"(slack);
431            add!"bytesUsed"(Signed!size_t(b.length - oldLength));
432            if (oldB == b.ptr)
433            {
434                // This was an in-place reallocation, yay
435                wasInPlace = true;
436                up!"numReallocateInPlace";
437                add!"bytesNotMoved"(oldLength);
438                delta = b.length - oldLength;
439                if (delta >= 0)
440                {
441                    // Expansion
442                    add!"bytesAllocated"(delta);
443                    add!"bytesExpanded"(delta);
444                }
445                else
446                {
447                    // Contraction
448                    add!"bytesContracted"(-delta);
449                }
450            }
451            else
452            {
453                // This was a allocate-move-deallocate cycle
454                add!"bytesAllocated"(b.length);
455                add!"bytesMoved"(oldLength);
456            }
457        }
458        addPerCall!(f, n, "numReallocate", "numReallocateOK",
459            "numReallocateInPlace", "bytesNotMoved",
460            "bytesExpanded", "bytesContracted", "bytesMoved")
461            (1, result, wasInPlace, wasInPlace ? oldLength : 0,
462                delta >= 0 ? delta : 0, delta < 0 ? -delta : 0,
463                wasInPlace ? 0 : oldLength);
464        return result;
465    }
466
467    /**
468    Defined whether or not $(D Allocator.deallocate) is defined. Affects
469    per instance: $(D numDeallocate), $(D bytesUsed), and $(D bytesSlack).
470    Affects per call: $(D numDeallocate) and $(D bytesContracted).
471    */
472    static if (!(perCallFlags &
473            (Options.numDeallocate | Options.bytesContracted)))
474        bool deallocate(void[] b)
475        { return deallocateImpl(b); }
476    else
477        bool deallocate(string f = __FILE__, uint n = __LINE__)(void[] b)
478        { return deallocateImpl!(f, n)(b); }
479
480    private bool deallocateImpl(string f = null, uint n = 0)(void[] b)
481    {
482        up!"numDeallocate";
483        add!"bytesUsed"(-Signed!size_t(b.length));
484        add!"bytesSlack"(-(this.goodAllocSize(b.length) - b.length));
485        addPerCall!(f, n, "numDeallocate", "bytesContracted")(1, b.length);
486        static if (hasMember!(Allocator, "deallocate"))
487            return parent.deallocate(b);
488        else
489            return false;
490    }
491
492    static if (hasMember!(Allocator, "deallocateAll"))
493    {
494        /**
495        Defined only if $(D Allocator.deallocateAll) is defined. Affects
496        per instance and per call $(D numDeallocateAll).
497        */
498        static if (!(perCallFlags & Options.numDeallocateAll))
499            bool deallocateAll()
500            { return deallocateAllImpl(); }
501        else
502            bool deallocateAll(string f = __FILE__, uint n = __LINE__)()
503            { return deallocateAllImpl!(f, n)(); }
504
505        private bool deallocateAllImpl(string f = null, uint n = 0)()
506        {
507            up!"numDeallocateAll";
508            addPerCall!(f, n, "numDeallocateAll")(1);
509            static if ((flags & Options.bytesUsed))
510                _bytesUsed = 0;
511            return parent.deallocateAll();
512        }
513    }
514
515    /**
516    Defined only if $(D Options.bytesUsed) is defined. Returns $(D bytesUsed ==
517    0).
518    */
519    static if (flags & Options.bytesUsed)
520    Ternary empty()
521    {
522        return Ternary(_bytesUsed == 0);
523    }
524
525    /**
526    Reports per instance statistics to $(D output) (e.g. $(D stdout)). The
527    format is simple: one kind and value per line, separated by a colon, e.g.
528    $(D bytesAllocated:7395404)
529    */
530    void reportStatistics(R)(auto ref R output)
531    {
532        import std.conv : to;
533        import std.traits : EnumMembers;
534        foreach (e; EnumMembers!Options)
535        {
536            static if ((flags & e) && e != Options.numAll
537                    && e != Options.bytesAll && e != Options.all)
538                output.write(e.to!string, ":", mixin(e.to!string), '\n');
539        }
540    }
541
542    static if (perCallFlags)
543    {
544        /**
545        Defined if $(D perCallFlags) is nonzero.
546        */
547        struct PerCallStatistics
548        {
549            /// The file and line of the call.
550            string file;
551            /// Ditto
552            uint line;
553            /// The options corresponding to the statistics collected.
554            Options[] opts;
555            /// The values of the statistics. Has the same length as $(D opts).
556            ulong[] values;
557            // Next in the chain.
558            private PerCallStatistics* next;
559
560            /**
561            Format to a string such as:
562            $(D mymodule.d(655): [numAllocate:21, numAllocateOK:21, bytesAllocated:324202]).
563            */
564            string toString() const
565            {
566                import std.conv : text, to;
567                auto result = text(file, "(", line, "): [");
568                foreach (i, opt; opts)
569                {
570                    if (i) result ~= ", ";
571                    result ~= opt.to!string;
572                    result ~= ':';
573                    result ~= values[i].to!string;
574                }
575                return result ~= "]";
576            }
577        }
578        private static PerCallStatistics* root;
579
580        /**
581        Defined if $(D perCallFlags) is nonzero. Iterates all monitored
582        file/line instances. The order of iteration is not meaningful (items
583        are inserted at the front of a list upon the first call), so
584        preprocessing the statistics after collection might be appropriate.
585        */
586        static auto byFileLine()
587        {
588            static struct Voldemort
589            {
590                PerCallStatistics* current;
591                bool empty() { return !current; }
592                ref PerCallStatistics front() { return *current; }
593                void popFront() { current = current.next; }
594                auto save() { return this; }
595            }
596            return Voldemort(root);
597        }
598
599        /**
600        Defined if $(D perCallFlags) is nonzero. Outputs (e.g. to a $(D File))
601        a simple report of the collected per-call statistics.
602        */
603        static void reportPerCallStatistics(R)(auto ref R output)
604        {
605            output.write("Stats for: ", StatsCollector.stringof, '\n');
606            foreach (ref stat; byFileLine)
607            {
608                output.write(stat, '\n');
609            }
610        }
611
612        private PerCallStatistics* statsAt(string f, uint n, opts...)()
613        {
614            import std.array : array;
615            import std.range : repeat;
616
617            static PerCallStatistics s = { f, n, [ opts ],
618                repeat(0UL, opts.length).array };
619            static bool inserted;
620
621            if (!inserted)
622            {
623                // Insert as root
624                s.next = root;
625                root = &s;
626                inserted = true;
627            }
628            return &s;
629        }
630
631        private void addPerCall(string f, uint n, names...)(ulong[] values...)
632        {
633            import std.array : join;
634            enum uint mask = mixin("Options."~[names].join("|Options."));
635            static if (perCallFlags & mask)
636            {
637                // Per allocation info
638                auto ps = mixin("statsAt!(f, n,"
639                    ~ "Options."~[names].join(", Options.")
640                ~")");
641                foreach (i; 0 .. names.length)
642                {
643                    ps.values[i] += values[i];
644                }
645            }
646        }
647    }
648    else
649    {
650        private void addPerCall(string f, uint n, names...)(ulong[]...)
651        {
652        }
653    }
654}
655
656///
657@system unittest
658{
659    import std.experimental.allocator.building_blocks.free_list : FreeList;
660    import std.experimental.allocator.gc_allocator : GCAllocator;
661    alias Allocator = StatsCollector!(GCAllocator, Options.all, Options.all);
662
663    Allocator alloc;
664    auto b = alloc.allocate(10);
665    alloc.reallocate(b, 20);
666    alloc.deallocate(b);
667
668    import std.file : deleteme, remove;
669    import std.range : walkLength;
670    import std.stdio : File;
671
672    auto f = deleteme ~ "-dlang.std.experimental.allocator.stats_collector.txt";
673    scope(exit) remove(f);
674    Allocator.reportPerCallStatistics(File(f, "w"));
675    alloc.reportStatistics(File(f, "a"));
676    assert(File(f).byLine.walkLength == 22);
677}
678
679@system unittest
680{
681    void test(Allocator)()
682    {
683        import std.range : walkLength;
684        import std.stdio : writeln;
685        Allocator a;
686        auto b1 = a.allocate(100);
687        assert(a.numAllocate == 1);
688        assert(a.expand(b1, 0));
689        assert(a.reallocate(b1, b1.length + 1));
690        auto b2 = a.allocate(101);
691        assert(a.numAllocate == 2);
692        assert(a.bytesAllocated == 202);
693        assert(a.bytesUsed == 202);
694        auto b3 = a.allocate(202);
695        assert(a.numAllocate == 3);
696        assert(a.bytesAllocated == 404);
697
698        a.deallocate(b2);
699        assert(a.numDeallocate == 1);
700        a.deallocate(b1);
701        assert(a.numDeallocate == 2);
702        a.deallocate(b3);
703        assert(a.numDeallocate == 3);
704        assert(a.numAllocate == a.numDeallocate);
705        assert(a.bytesUsed == 0);
706     }
707
708    import std.experimental.allocator.building_blocks.free_list : FreeList;
709    import std.experimental.allocator.gc_allocator : GCAllocator;
710    test!(StatsCollector!(GCAllocator, Options.all, Options.all));
711    test!(StatsCollector!(FreeList!(GCAllocator, 128), Options.all,
712        Options.all));
713}
714
715@system unittest
716{
717    void test(Allocator)()
718    {
719        import std.range : walkLength;
720        import std.stdio : writeln;
721        Allocator a;
722        auto b1 = a.allocate(100);
723        assert(a.expand(b1, 0));
724        assert(a.reallocate(b1, b1.length + 1));
725        auto b2 = a.allocate(101);
726        auto b3 = a.allocate(202);
727
728        a.deallocate(b2);
729        a.deallocate(b1);
730        a.deallocate(b3);
731    }
732    import std.experimental.allocator.building_blocks.free_list : FreeList;
733    import std.experimental.allocator.gc_allocator : GCAllocator;
734    test!(StatsCollector!(GCAllocator, 0, 0));
735}
736