jdk

Форк
0
/
compilationMemoryStatistic.cpp 
638 строк · 19.7 Кб
1
/*
2
 * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
3
 * Copyright (c) 2023, 2024, Red Hat, Inc. and/or its affiliates.
4
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5
 *
6
 * This code is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU General Public License version 2 only, as
8
 * published by the Free Software Foundation.
9
 *
10
 * This code is distributed in the hope that it will be useful, but WITHOUT
11
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13
 * version 2 for more details (a copy is included in the LICENSE file that
14
 * accompanied this code).
15
 *
16
 * You should have received a copy of the GNU General Public License version
17
 * 2 along with this work; if not, write to the Free Software Foundation,
18
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19
 *
20
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
21
 * or visit www.oracle.com if you need additional information or have any
22
 * questions.
23
 *
24
 */
25

26
#include "precompiled.hpp"
27
#include "logging/log.hpp"
28
#include "logging/logStream.hpp"
29
#ifdef COMPILER1
30
#include "c1/c1_Compilation.hpp"
31
#endif
32
#include "compiler/abstractCompiler.hpp"
33
#include "compiler/compilationMemoryStatistic.hpp"
34
#include "compiler/compilerDirectives.hpp"
35
#include "compiler/compileTask.hpp"
36
#include "compiler/compilerDefinitions.hpp"
37
#include "compiler/compilerThread.hpp"
38
#include "memory/arena.hpp"
39
#include "memory/resourceArea.hpp"
40
#include "nmt/nmtCommon.hpp"
41
#include "oops/symbol.hpp"
42
#ifdef COMPILER2
43
#include "opto/node.hpp" // compile.hpp is not self-contained
44
#include "opto/compile.hpp"
45
#endif
46
#include "runtime/mutexLocker.hpp"
47
#include "runtime/os.hpp"
48
#include "utilities/debug.hpp"
49
#include "utilities/globalDefinitions.hpp"
50
#include "utilities/ostream.hpp"
51
#include "utilities/quickSort.hpp"
52
#include "utilities/resourceHash.hpp"
53

54
ArenaStatCounter::ArenaStatCounter() :
55
  _current(0), _start(0), _peak(0),
56
  _na(0), _ra(0),
57
  _limit(0), _hit_limit(false), _limit_in_process(false),
58
  _na_at_peak(0), _ra_at_peak(0), _live_nodes_at_peak(0)
59
{}
60

61
size_t ArenaStatCounter::peak_since_start() const {
62
  return _peak > _start ? _peak - _start : 0;
63
}
64

65
void ArenaStatCounter::start(size_t limit) {
66
  _peak = _start = _current;
67
  _limit = limit;
68
  _hit_limit = false;
69
}
70

71
void ArenaStatCounter::end(){
72
  _limit = 0;
73
  _hit_limit = false;
74
}
75

76
void ArenaStatCounter::update_c2_node_count() {
77
#ifdef COMPILER2
78
  CompilerThread* const th = Thread::current()->as_Compiler_thread();
79
  const CompileTask* const task = th->task();
80
  if (task != nullptr &&
81
      th->task()->compiler() != nullptr &&
82
      th->task()->compiler()->type() == compiler_c2) {
83
    const Compile* const comp = Compile::current();
84
    if (comp != nullptr) {
85
      _live_nodes_at_peak = comp->live_nodes();
86
    }
87
  }
88
#endif
89
}
90

91
// Account an arena allocation or de-allocation.
92
bool ArenaStatCounter::account(ssize_t delta, int tag) {
93
  bool rc = false;
94
#ifdef ASSERT
95
  // Note: if this fires, we free more arena memory under the scope of the
96
  // CompilationMemoryHistoryMark than we allocate. This cannot be since we
97
  // assume arena allocations in CompilerThread to be stack bound and symmetric.
98
  assert(delta >= 0 || ((ssize_t)_current + delta) >= 0,
99
         "Negative overflow (d=%zd %zu %zu %zu)", delta, _current, _start, _peak);
100
#endif
101
  // Update totals
102
  _current += delta;
103
  // Update detail counter
104
  switch ((Arena::Tag)tag) {
105
    case Arena::Tag::tag_ra: _ra += delta; break;
106
    case Arena::Tag::tag_node: _na += delta; break;
107
    default: // ignore
108
      break;
109
  };
110
  // Did we reach a peak?
111
  if (_current > _peak) {
112
    _peak = _current;
113
    assert(delta > 0, "Sanity (%zu %zu %zu)", _current, _start, _peak);
114
    _na_at_peak = _na;
115
    _ra_at_peak = _ra;
116
    update_c2_node_count();
117
    rc = true;
118
    // Did we hit the memory limit?
119
    if (!_hit_limit && _limit > 0 && peak_since_start() > _limit) {
120
      _hit_limit = true;
121
    }
122
  }
123
  return rc;
124
}
125

126
void ArenaStatCounter::print_on(outputStream* st) const {
127
  st->print("%zu [na %zu ra %zu]", peak_since_start(), _na_at_peak, _ra_at_peak);
128
#ifdef ASSERT
129
  st->print(" (%zu->%zu->%zu)", _start, _peak, _current);
130
#endif
131
}
132

133
//////////////////////////
134
// Backend
135

136
class FullMethodName {
137
  Symbol* const _k;
138
  Symbol* const _m;
139
  Symbol* const _s;
140

141
public:
142

143
  FullMethodName(const Method* m) :
144
    _k(m->klass_name()), _m(m->name()), _s(m->signature()) {};
145
  FullMethodName(const FullMethodName& o) : _k(o._k), _m(o._m), _s(o._s) {}
146

147
  void make_permanent() {
148
    _k->make_permanent();
149
    _m->make_permanent();
150
    _s->make_permanent();
151
  }
152

153
  static unsigned compute_hash(const FullMethodName& n) {
154
    return Symbol::compute_hash(n._k) ^
155
        Symbol::compute_hash(n._m) ^
156
        Symbol::compute_hash(n._s);
157
  }
158

159
  char* as_C_string(char* buf, size_t len) const {
160
    stringStream ss(buf, len);
161
    ResourceMark rm;
162
    ss.print_raw(_k->as_C_string());
163
    ss.print_raw("::");
164
    ss.print_raw(_m->as_C_string());
165
    ss.put('(');
166
    ss.print_raw(_s->as_C_string());
167
    ss.put(')');
168
    return buf;
169
  }
170
  bool operator== (const FullMethodName& b) const {
171
    return _k == b._k && _m == b._m && _s == b._s;
172
  }
173
};
174

175
// Note: not mtCompiler since we don't want to change what we measure
176
class MemStatEntry : public CHeapObj<mtInternal> {
177
  const FullMethodName _method;
178
  CompilerType _comptype;
179
  double _time;
180
  // How often this has been recompiled.
181
  int _num_recomp;
182
  // Compiling thread. Only for diagnostic purposes. Thread may not be alive anymore.
183
  const Thread* _thread;
184
  // active limit for this compilation, if any
185
  size_t _limit;
186

187
  // peak usage, bytes, over all arenas
188
  size_t _total;
189
  // usage in node arena when total peaked
190
  size_t _na_at_peak;
191
  // usage in resource area when total peaked
192
  size_t _ra_at_peak;
193
  // number of nodes (c2 only) when total peaked
194
  unsigned _live_nodes_at_peak;
195
  const char* _result;
196

197
public:
198

199
  MemStatEntry(FullMethodName method)
200
    : _method(method), _comptype(compiler_c1),
201
      _time(0), _num_recomp(0), _thread(nullptr), _limit(0),
202
      _total(0), _na_at_peak(0), _ra_at_peak(0), _live_nodes_at_peak(0),
203
      _result(nullptr) {
204
  }
205

206
  void set_comptype(CompilerType comptype) { _comptype = comptype; }
207
  void set_current_time() { _time = os::elapsedTime(); }
208
  void set_current_thread() { _thread = Thread::current(); }
209
  void set_limit(size_t limit) { _limit = limit; }
210
  void inc_recompilation() { _num_recomp++; }
211

212
  void set_total(size_t n) { _total = n; }
213
  void set_na_at_peak(size_t n) { _na_at_peak = n; }
214
  void set_ra_at_peak(size_t n) { _ra_at_peak = n; }
215
  void set_live_nodes_at_peak(unsigned n) { _live_nodes_at_peak = n; }
216

217
  void set_result(const char* s) { _result = s; }
218

219
  size_t total() const { return _total; }
220

221
  static void print_legend(outputStream* st) {
222
    st->print_cr("Legend:");
223
    st->print_cr("  total  : memory allocated via arenas while compiling");
224
    st->print_cr("  NA     : ...how much in node arenas (if c2)");
225
    st->print_cr("  RA     : ...how much in resource areas");
226
    st->print_cr("  result : Result: 'ok' finished successfully, 'oom' hit memory limit, 'err' compilation failed");
227
    st->print_cr("  #nodes : ...how many nodes (c2 only)");
228
    st->print_cr("  limit  : memory limit, if set");
229
    st->print_cr("  time   : time of last compilation (sec)");
230
    st->print_cr("  type   : compiler type");
231
    st->print_cr("  #rc    : how often recompiled");
232
    st->print_cr("  thread : compiler thread");
233
  }
234

235
  static void print_header(outputStream* st) {
236
    st->print_cr("total     NA        RA        result  #nodes  limit   time    type  #rc thread              method");
237
  }
238

239
  void print_on(outputStream* st, bool human_readable) const {
240
    int col = 0;
241

242
    // Total
243
    if (human_readable) {
244
      st->print(PROPERFMT " ", PROPERFMTARGS(_total));
245
    } else {
246
      st->print("%zu ", _total);
247
    }
248
    col += 10; st->fill_to(col);
249

250
    // NA
251
    if (human_readable) {
252
      st->print(PROPERFMT " ", PROPERFMTARGS(_na_at_peak));
253
    } else {
254
      st->print("%zu ", _na_at_peak);
255
    }
256
    col += 10; st->fill_to(col);
257

258
    // RA
259
    if (human_readable) {
260
      st->print(PROPERFMT " ", PROPERFMTARGS(_ra_at_peak));
261
    } else {
262
      st->print("%zu ", _ra_at_peak);
263
    }
264
    col += 10; st->fill_to(col);
265

266
    // result?
267
    st->print("%s ", _result ? _result : "");
268
    col += 8; st->fill_to(col);
269

270
    // Number of Nodes when memory peaked
271
    if (_live_nodes_at_peak > 0) {
272
      st->print("%u ", _live_nodes_at_peak);
273
    } else {
274
      st->print("-");
275
    }
276
    col += 8; st->fill_to(col);
277

278
    // Limit
279
    if (_limit > 0) {
280
      st->print(PROPERFMT " ", PROPERFMTARGS(_limit));
281
    } else {
282
      st->print("-");
283
    }
284
    col += 8; st->fill_to(col);
285

286
    // TimeStamp
287
    st->print("%.3f ", _time);
288
    col += 8; st->fill_to(col);
289

290
    // Type
291
    st->print("%s ", compilertype2name(_comptype));
292
    col += 6; st->fill_to(col);
293

294
    // Recomp
295
    st->print("%u ", _num_recomp);
296
    col += 4; st->fill_to(col);
297

298
    // Thread
299
    st->print(PTR_FORMAT "  ", p2i(_thread));
300

301
    // MethodName
302
    char buf[1024];
303
    st->print("%s ", _method.as_C_string(buf, sizeof(buf)));
304
    st->cr();
305
  }
306

307
  int compare_by_size(const MemStatEntry* b) const {
308
    const size_t x1 = b->_total;
309
    const size_t x2 = _total;
310
    return x1 < x2 ? -1 : x1 == x2 ? 0 : 1;
311
  }
312
};
313

314
// The MemStatTable contains records of memory usage of all compilations. It is printed,
315
// as memory summary, either with jcmd Compiler.memory, or - if the "print" suboption has
316
// been given with the MemStat compile command - as summary printout at VM exit.
317
// For any given compiled method, we only keep the memory statistics of the most recent
318
// compilation, but on a per-compiler basis. If one needs statistics of prior compilations,
319
// one needs to look into the log produced by the "print" suboption.
320

321
class MemStatTableKey {
322
  const FullMethodName _fmn;
323
  const CompilerType _comptype;
324
public:
325
  MemStatTableKey(FullMethodName fmn, CompilerType comptype) :
326
    _fmn(fmn), _comptype(comptype) {}
327
  MemStatTableKey(const MemStatTableKey& o) :
328
    _fmn(o._fmn), _comptype(o._comptype) {}
329
  bool operator== (const MemStatTableKey& other) const {
330
    return _fmn == other._fmn && _comptype == other._comptype;
331
  }
332
  static unsigned compute_hash(const MemStatTableKey& n) {
333
    return FullMethodName::compute_hash(n._fmn) + (unsigned)n._comptype;
334
  }
335
};
336

337
class MemStatTable :
338
    public ResourceHashtable<MemStatTableKey, MemStatEntry*, 7919, AnyObj::C_HEAP,
339
                             mtInternal, MemStatTableKey::compute_hash>
340
{
341
public:
342

343
  void add(const FullMethodName& fmn, CompilerType comptype,
344
           size_t total, size_t na_at_peak, size_t ra_at_peak,
345
           unsigned live_nodes_at_peak, size_t limit, const char* result) {
346
    assert_lock_strong(NMTCompilationCostHistory_lock);
347
    MemStatTableKey key(fmn, comptype);
348
    MemStatEntry** pe = get(key);
349
    MemStatEntry* e = nullptr;
350
    if (pe == nullptr) {
351
      e = new MemStatEntry(fmn);
352
      put(key, e);
353
    } else {
354
      // Update existing entry
355
      e = *pe;
356
      assert(e != nullptr, "Sanity");
357
    }
358
    e->set_current_time();
359
    e->set_current_thread();
360
    e->set_comptype(comptype);
361
    e->inc_recompilation();
362
    e->set_total(total);
363
    e->set_na_at_peak(na_at_peak);
364
    e->set_ra_at_peak(ra_at_peak);
365
    e->set_live_nodes_at_peak(live_nodes_at_peak);
366
    e->set_limit(limit);
367
    e->set_result(result);
368
  }
369

370
  // Returns a C-heap-allocated SortMe array containing all entries from the table,
371
  // optionally filtered by entry size
372
  MemStatEntry** calc_flat_array(int& num, size_t min_size) {
373
    assert_lock_strong(NMTCompilationCostHistory_lock);
374

375
    const int num_all = number_of_entries();
376
    MemStatEntry** flat = NEW_C_HEAP_ARRAY(MemStatEntry*, num_all, mtInternal);
377
    int i = 0;
378
    auto do_f = [&] (const MemStatTableKey& ignored, MemStatEntry* e) {
379
      if (e->total() >= min_size) {
380
        flat[i] = e;
381
        assert(i < num_all, "Sanity");
382
        i ++;
383
      }
384
    };
385
    iterate_all(do_f);
386
    if (min_size == 0) {
387
      assert(i == num_all, "Sanity");
388
    } else {
389
      assert(i <= num_all, "Sanity");
390
    }
391
    num = i;
392
    return flat;
393
  }
394
};
395

396
bool CompilationMemoryStatistic::_enabled = false;
397

398
static MemStatTable* _the_table = nullptr;
399

400
void CompilationMemoryStatistic::initialize() {
401
  assert(_enabled == false && _the_table == nullptr, "Only once");
402
  _the_table = new (mtCompiler) MemStatTable;
403
  _enabled = true;
404
  log_info(compilation, alloc)("Compilation memory statistic enabled");
405
}
406

407
void CompilationMemoryStatistic::on_start_compilation(const DirectiveSet* directive) {
408
  assert(enabled(), "Not enabled?");
409
  const size_t limit = directive->mem_limit();
410
  Thread::current()->as_Compiler_thread()->arena_stat()->start(limit);
411
}
412

413
void CompilationMemoryStatistic::on_end_compilation() {
414
  assert(enabled(), "Not enabled?");
415
  ResourceMark rm;
416
  CompilerThread* const th = Thread::current()->as_Compiler_thread();
417
  ArenaStatCounter* const arena_stat = th->arena_stat();
418
  CompileTask* const task = th->task();
419
  const CompilerType ct = task->compiler()->type();
420

421
  const Method* const m = th->task()->method();
422
  FullMethodName fmn(m);
423
  fmn.make_permanent();
424

425
  const DirectiveSet* directive = th->task()->directive();
426
  assert(directive->should_collect_memstat(), "Should only be called if memstat is enabled for this method");
427
  const bool print = directive->should_print_memstat();
428

429
  // Store memory used in task, for later processing by JFR
430
  task->set_arena_bytes(arena_stat->peak_since_start());
431

432
  // Store result
433
  // For this to work, we must call on_end_compilation() at a point where
434
  // Compile|Compilation already handed over the failure string to ciEnv,
435
  // but ciEnv must still be alive.
436
  const char* result = "ok"; // ok
437
  const ciEnv* const env = th->env();
438
  if (env) {
439
    const char* const failure_reason = env->failure_reason();
440
    if (failure_reason != nullptr) {
441
      result = (strcmp(failure_reason, failure_reason_memlimit()) == 0) ? "oom" : "err";
442
    }
443
  }
444

445
  {
446
    MutexLocker ml(NMTCompilationCostHistory_lock, Mutex::_no_safepoint_check_flag);
447
    assert(_the_table != nullptr, "not initialized");
448

449
    _the_table->add(fmn, ct,
450
                    arena_stat->peak_since_start(), // total
451
                    arena_stat->na_at_peak(),
452
                    arena_stat->ra_at_peak(),
453
                    arena_stat->live_nodes_at_peak(),
454
                    arena_stat->limit(),
455
                    result);
456
  }
457
  if (print) {
458
    char buf[1024];
459
    fmn.as_C_string(buf, sizeof(buf));
460
    tty->print("%s Arena usage %s: ", compilertype2name(ct), buf);
461
    arena_stat->print_on(tty);
462
    tty->cr();
463
  }
464

465
  arena_stat->end(); // reset things
466
}
467

468
static void inform_compilation_about_oom(CompilerType ct) {
469
  // Inform C1 or C2 that an OOM happened. They will take delayed action
470
  // and abort the compilation in progress. Note that this is not instantaneous,
471
  // since the compiler has to actively bailout, which may take a while, during
472
  // which memory usage may rise further.
473
  //
474
  // The mechanism differs slightly between C1 and C2:
475
  // - With C1, we directly set the bailout string, which will cause C1 to
476
  //   bailout at the typical BAILOUT places.
477
  // - With C2, the corresponding mechanism would be the failure string; but
478
  //   bailout paths in C2 are not complete and therefore it is dangerous to
479
  //   set the failure string at - for C2 - seemingly random places. Instead,
480
  //   upon OOM C2 sets the failure string next time it checks the node limit.
481
  if (ciEnv::current() != nullptr) {
482
    void* compiler_data = ciEnv::current()->compiler_data();
483
#ifdef COMPILER1
484
    if (ct == compiler_c1) {
485
      Compilation* C = static_cast<Compilation*>(compiler_data);
486
      if (C != nullptr) {
487
        C->bailout(CompilationMemoryStatistic::failure_reason_memlimit());
488
        C->set_oom();
489
      }
490
    }
491
#endif
492
#ifdef COMPILER2
493
    if (ct == compiler_c2) {
494
      Compile* C = static_cast<Compile*>(compiler_data);
495
      if (C != nullptr) {
496
        C->set_oom();
497
      }
498
    }
499
#endif // COMPILER2
500
  }
501
}
502

503
void CompilationMemoryStatistic::on_arena_change(ssize_t diff, const Arena* arena) {
504
  assert(enabled(), "Not enabled?");
505
  CompilerThread* const th = Thread::current()->as_Compiler_thread();
506

507
  ArenaStatCounter* const arena_stat = th->arena_stat();
508
  if (arena_stat->limit_in_process()) {
509
    return; // avoid recursion on limit hit
510
  }
511

512
  bool hit_limit_before = arena_stat->hit_limit();
513

514
  if (arena_stat->account(diff, (int)arena->get_tag())) { // new peak?
515

516
    // Limit handling
517
    if (arena_stat->hit_limit()) {
518
      char name[1024] = "";
519
      bool print = false;
520
      bool crash = false;
521
      CompilerType ct = compiler_none;
522

523
      arena_stat->set_limit_in_process(true); // prevent recursive limit hits
524

525
      // get some more info
526
      const CompileTask* const task = th->task();
527
      if (task != nullptr) {
528
        ct = task->compiler()->type();
529
        const DirectiveSet* directive = task->directive();
530
        print = directive->should_print_memstat();
531
        crash = directive->should_crash_at_mem_limit();
532
        const Method* m = th->task()->method();
533
        if (m != nullptr) {
534
          FullMethodName(m).as_C_string(name, sizeof(name));
535
        }
536
      }
537

538
      char message[1024] = "";
539

540
      // build up message if we need it later
541
      if (print || crash) {
542
        stringStream ss(message, sizeof(message));
543
        if (ct != compiler_none && name[0] != '\0') {
544
          ss.print("%s %s: ", compilertype2name(ct), name);
545
        }
546
        ss.print("Hit MemLimit %s(limit: %zu now: %zu)",
547
                 (hit_limit_before ? "again " : ""),
548
                 arena_stat->limit(), arena_stat->peak_since_start());
549
      }
550

551
      // log if needed
552
      if (print) {
553
        tty->print_raw(message);
554
        tty->cr();
555
      }
556

557
      // Crash out if needed
558
      if (crash) {
559
        report_fatal(OOM_HOTSPOT_ARENA, __FILE__, __LINE__, "%s", message);
560
      } else {
561
        inform_compilation_about_oom(ct);
562
      }
563

564
      arena_stat->set_limit_in_process(false);
565
    }
566
  }
567
}
568

569
static inline ssize_t diff_entries_by_size(const MemStatEntry* e1, const MemStatEntry* e2) {
570
  return e1->compare_by_size(e2);
571
}
572

573
void CompilationMemoryStatistic::print_all_by_size(outputStream* st, bool human_readable, size_t min_size) {
574

575
  MutexLocker ml(NMTCompilationCostHistory_lock, Mutex::_no_safepoint_check_flag);
576

577
  st->cr();
578
  st->print_cr("Compilation memory statistics");
579

580
  if (!enabled()) {
581
    st->print_cr("(unavailable)");
582
    return;
583
  }
584

585
  st->cr();
586

587
  MemStatEntry::print_legend(st);
588
  st->cr();
589

590
  if (min_size > 0) {
591
    st->print_cr(" (cutoff: %zu bytes)", min_size);
592
  }
593
  st->cr();
594

595
  MemStatEntry::print_header(st);
596

597
  MemStatEntry** filtered = nullptr;
598

599
  if (_the_table != nullptr) {
600
    // We sort with quicksort
601
    int num = 0;
602
    filtered = _the_table->calc_flat_array(num, min_size);
603
    if (min_size > 0) {
604
      st->print_cr("(%d/%d)", num, _the_table->number_of_entries());
605
    }
606
    if (num > 0) {
607
      QuickSort::sort(filtered, num, diff_entries_by_size);
608
      // Now print. Has to happen under lock protection too, since entries may be changed.
609
      for (int i = 0; i < num; i ++) {
610
        filtered[i]->print_on(st, human_readable);
611
      }
612
    } else {
613
      st->print_cr("No entries.");
614
    }
615
  } else {
616
    st->print_cr("Not initialized.");
617
  }
618
  st->cr();
619

620
  FREE_C_HEAP_ARRAY(Entry, filtered);
621
}
622

623
const char* CompilationMemoryStatistic::failure_reason_memlimit() {
624
  static const char* const s = "hit memory limit while compiling";
625
  return s;
626
}
627

628
CompilationMemoryStatisticMark::CompilationMemoryStatisticMark(const DirectiveSet* directive)
629
  : _active(directive->should_collect_memstat()) {
630
  if (_active) {
631
    CompilationMemoryStatistic::on_start_compilation(directive);
632
  }
633
}
634
CompilationMemoryStatisticMark::~CompilationMemoryStatisticMark() {
635
  if (_active) {
636
    CompilationMemoryStatistic::on_end_compilation();
637
  }
638
}
639

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.