Add new malloc benchmarks.

This runs through the trace of the allocations in a sql benchmark app
executed in the benchmark thread.

Add one benchmark with decay time set to 0 and another with decay time
set to 1.

Include a script that can generate a header file that can be used to
regenerate the data.

Bug: 112317428

Test: Builds, ran unit tests, ran benchmarks.
Change-Id: I62e287cc06b74b74bcc5a4bbee71b0fac0a196fd
This commit is contained in:
Christopher Ferris 2018-08-09 15:19:57 -07:00
parent 5285d5995b
commit 0dc784431b
5 changed files with 535976 additions and 0 deletions

View file

@ -29,6 +29,7 @@ cc_defaults {
"bionic_benchmarks.cpp",
"atomic_benchmark.cpp",
"inttypes_benchmark.cpp",
"malloc_benchmark.cpp",
"math_benchmark.cpp",
"property_benchmark.cpp",
"pthread_benchmark.cpp",

View file

@ -0,0 +1,123 @@
/*
* Copyright (C) 2018 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <malloc.h>
#include <stdlib.h>
#include <benchmark/benchmark.h>
#include "util.h"
#if defined(__BIONIC__)
enum AllocEnum : uint8_t {
MALLOC = 0,
CALLOC,
MEMALIGN,
REALLOC,
FREE,
};
struct MallocEntry {
AllocEnum type;
size_t idx;
size_t size;
size_t arg2;
};
void BenchmarkMalloc(MallocEntry entries[], size_t total_entries, size_t max_allocs) {
void* ptrs[max_allocs];
for (size_t i = 0; i < total_entries; i++) {
switch (entries[i].type) {
case MALLOC:
ptrs[entries[i].idx] = malloc(entries[i].size);
// Touch at least one byte of the allocation to make sure that
// PSS for this allocation is counted.
reinterpret_cast<uint8_t*>(ptrs[entries[i].idx])[0] = 10;
break;
case CALLOC:
ptrs[entries[i].idx] = calloc(entries[i].arg2, entries[i].size);
// Touch at least one byte of the allocation to make sure that
// PSS for this allocation is counted.
reinterpret_cast<uint8_t*>(ptrs[entries[i].idx])[0] = 20;
break;
case MEMALIGN:
ptrs[entries[i].idx] = memalign(entries[i].arg2, entries[i].size);
// Touch at least one byte of the allocation to make sure that
// PSS for this allocation is counted.
reinterpret_cast<uint8_t*>(ptrs[entries[i].idx])[0] = 30;
break;
case REALLOC:
if (entries[i].arg2 == 0) {
ptrs[entries[i].idx] = realloc(nullptr, entries[i].size);
} else {
ptrs[entries[i].idx] = realloc(ptrs[entries[i].arg2 - 1], entries[i].size);
}
// Touch at least one byte of the allocation to make sure that
// PSS for this allocation is counted.
reinterpret_cast<uint8_t*>(ptrs[entries[i].idx])[0] = 40;
break;
case FREE:
free(ptrs[entries[i].idx]);
break;
}
}
}
// This codifies playing back a single threaded trace of the allocations
// when running the SQLite BenchMark app.
// Instructions for recreating:
// - Enable malloc debug
// setprop wrap.com.wtsang02.sqliteutil "LIBC_DEBUG_MALLOC_OPTIONS=record_allocs logwrapper"
// - Start the SQLite BenchMark app
// - Dump allocs using the signal to get rid of non sql allocs(kill -47 <SQLITE_PID>)
// - Run the benchmark.
// - Dump allocs using the signal again.
// - Find the thread that has the most allocs and run the helper script
// bionic/libc/malloc_debug/tools/gen_malloc.pl -i <THREAD_ID> g_sql_entries kMaxSqlAllocSlots < <ALLOC_FILE> > malloc_sql.h
#include "malloc_sql.h"
static void BM_malloc_sql_trace_decay_time_0(benchmark::State& state) {
mallopt(M_DECAY_TIME, 0);
for (auto _ : state) {
BenchmarkMalloc(g_sql_entries, sizeof(g_sql_entries) / sizeof(MallocEntry),
kMaxSqlAllocSlots);
}
}
BIONIC_BENCHMARK(BM_malloc_sql_trace_decay_time_0);
static void BM_malloc_sql_trace_decay_time_1(benchmark::State& state) {
mallopt(M_DECAY_TIME, 1);
for (auto _ : state) {
BenchmarkMalloc(g_sql_entries, sizeof(g_sql_entries) / sizeof(MallocEntry),
kMaxSqlAllocSlots);
}
}
BIONIC_BENCHMARK(BM_malloc_sql_trace_decay_time_1);
#endif

535516
benchmarks/malloc_sql.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -178,6 +178,8 @@ TEST_F(SystemTests, all_benchmarks) {
"BM_atomic_store_seq_cst/iterations:1\n"
"BM_inttypes_strtoimax/iterations:1\n"
"BM_inttypes_strtoumax/iterations:1\n"
"BM_malloc_sql_trace_decay_time_0/iterations:1\n"
"BM_malloc_sql_trace_decay_time_1/iterations:1\n"
"BM_math_cosf/0/iterations:1\n"
"BM_math_cosf/1/iterations:1\n"
"BM_math_cosf/2/iterations:1\n"

View file

@ -0,0 +1,334 @@
#!/usr/bin/perl -w
# Copyright (C) 2018 The Android Open Source Project
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
use strict;
sub PrintHeader() {
print <<EOT;
/*
* Copyright (C) 2018 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
// Generated by gen_malloc.pl, do not modify.
EOT
}
sub PrintMainloop() {
print <<EOT;
void BenchmarkMalloc(MallocEntry entries[], size_t total_entries, size_t max_allocs) {
void* ptrs[max_allocs];
for (size_t i = 0; i < total_entries; i++) {
switch (entries[i].type) {
case MALLOC:
ptrs[entries[i].idx] = malloc(entries[i].size);
// Touch at least one byte of the allocation to make sure that
// PSS for this allocation is counted.
reinterpret_cast<uint8_t*>(ptrs[entries[i].idx])[0] = 10;
break;
case CALLOC:
ptrs[entries[i].idx] = calloc(entries[i].arg2, entries[i].size);
// Touch at least one byte of the allocation to make sure that
// PSS for this allocation is counted.
reinterpret_cast<uint8_t*>(ptrs[entries[i].idx])[0] = 20;
break;
case MEMALIGN:
ptrs[entries[i].idx] = memalign(entries[i].arg2, entries[i].size);
// Touch at least one byte of the allocation to make sure that
// PSS for this allocation is counted.
reinterpret_cast<uint8_t*>(ptrs[entries[i].idx])[0] = 30;
break;
case REALLOC:
if (entries[i].arg2 == 0) {
ptrs[entries[i].idx] = realloc(nullptr, entries[i].size);
} else {
ptrs[entries[i].idx] = realloc(ptrs[entries[i].arg2 - 1], entries[i].size);
}
// Touch at least one byte of the allocation to make sure that
// PSS for this allocation is counted.
reinterpret_cast<uint8_t*>(ptrs[entries[i].idx])[0] = 40;
break;
case FREE:
free(ptrs[entries[i].idx]);
break;
}
}
}
EOT
}
sub PrintDefinitions() {
print <<EOT;
enum AllocEnum : uint8_t {
MALLOC = 0,
CALLOC,
MEMALIGN,
REALLOC,
FREE,
};
struct MallocEntry {
AllocEnum type;
size_t idx;
size_t size;
size_t arg2;
};
EOT
}
sub PrintUsageAndExit() {
print "USAGE: gen_malloc.pl [-d][-i][-m] THREAD_ID STRUCT_NAME MAX_SLOT_NAME < ALLOCS.txt\n";
print " -d\n";
print " Print the structure definitions.\n";
print " -i\n";
print " Ignore missing allocations.\n";
print " -m\n";
print " Print the main loop code that can reproduce the trace.\n";
print " THREAD_ID\n";
print " The thread for which entries will be printed.\n";
print " STRUCT_NAME\n";
print " The name of the structure containing all of the entries.\n";
print " MAX_SLOT_NAME\n";
print " The name of the name of the maximum slots variable.\n";
print " ALLOCS.txt\n";
print " A file generated by the malloc debug option record_allocs\n";
exit(1);
}
sub GetSlot($) {
my ($opts) = @_;
if (scalar(@{$opts->{empty_slots}}) == 0) {
return $opts->{last_slot}++;
} else {
return pop(@{$opts->{empty_slots}});
}
}
sub PrintFreeSlots($) {
my ($opts) = @_;
if (scalar(@{$opts->{empty_slots}}) == $opts->{last_slot}) {
return;
}
print "\n // Free rest of the allocs.\n";
my @sorted_empty_slots = sort({$a <=> $b} @{$opts->{empty_slots}});
my $slot = 0;
my $last_slot = $opts->{last_slot};
while ($slot < $last_slot) {
my $empty_slot = $last_slot;
if (scalar(@sorted_empty_slots) != 0) {
$empty_slot = shift(@sorted_empty_slots);
}
for (; $slot < $empty_slot; $slot++) {
print " {FREE, $slot, 0, 0},\n";
}
$slot++;
}
}
sub PrintAlloc($$$$$$) {
my ($opts, $cur_thread, $pointer, $name, $size, $arg2) = @_;
if ($opts->{thread} eq $cur_thread) {
my $slot = GetSlot($opts);
$opts->{pointers}->{$pointer} = $slot;
print " {$name, $slot, $size, $arg2},\n";
} else {
$opts->{pointers}->{$pointer} = -1;
}
}
sub PrintEntries($$) {
my ($thread, $ignore_missing_allocations) = @_;
my $opts = {};
$opts->{thread} = $thread;
$opts->{empty_slots} = [];
$opts->{last_slot} = 0;
$opts->{pointers} = {};
while (<>) {
if (!/^(\d+):\s*/) {
continue
}
my $cur_thread = $1;
$_ = $';
if (/^malloc\s+(\S+)\s+(\d+)/) {
my $pointer = $1;
my $size = $2;
PrintAlloc($opts, $cur_thread, $pointer, "MALLOC", $size, 0);
} elsif (/^calloc\s+(\S+)\s+(\d+)\s+(\d+)/) {
my $pointer = $1;
my $nmemb = $2;
my $size = $3;
PrintAlloc($opts, $cur_thread, $pointer, "CALLOC", $size, $nmemb);
} elsif (/^memalign\s+(\S+)\s+(\d+)\s+(\d+)/) {
my $pointer = $1;
my $align = $2;
my $size = $3;
PrintAlloc($opts, $cur_thread, $pointer, "MEMALIGN", $size, $align);
} elsif (/^free\s+(\S+)/) {
my $pointer = $1;
if (!exists $opts->{pointers}->{$pointer}) {
if ($ignore_missing_allocations) {
warn "WARNING: $.: Unknown allocation $pointer ignored on $cur_thread\n";
next;
} else {
die "$.: Unknown allocation $pointer on $cur_thread\n";
}
} elsif ($opts->{pointers}->{$pointer} != -1) {
print " {FREE, $opts->{pointers}->{$pointer}, 0, 0},\n";
push @{$opts->{empty_slots}}, $opts->{pointers}->{$pointer};
}
} elsif (/^realloc\s+(\S+)\s+(\S+)\s+(\d+)/) {
my $new_pointer = $1;
my $old_pointer = $2;
my $size = $3;
if ($thread ne $cur_thread) {
if ($new_pointer ne $old_pointer) {
$opts->{pointers}->{$new_pointer} = -1;
delete $opts->{pointers}->{$old_pointer};
}
} elsif ($old_pointer eq "0x0") {
my $slot = GetSlot($opts);
# This was a realloc(nullptr, size) call.
print " {REALLOC, $slot, $size, 0},\n";
$opts->{pointers}->{$new_pointer} = $slot;
} else {
if (!exists $opts->{pointers}->{$old_pointer}) {
if ($ignore_missing_allocations) {
warn "WARNING: $.: Unknown realloc allocation $old_pointer ignored on $cur_thread\n";
next;
} else {
die "Unknown realloc allocation $old_pointer on $cur_thread\n";
}
}
if ($opts->{pointers}->{$old_pointer} != -1) {
# Reuse the same slot, no need to get a new one.
my $slot = $opts->{pointers}->{$old_pointer};
printf(" {REALLOC, $slot, $size, %d},\n", $slot + 1);
# NOTE: It is possible that old pointer and new pointer are the
# same (a realloc returns the same pointer).
if ($new_pointer ne $old_pointer) {
$opts->{pointers}->{$new_pointer} = $slot;
delete $opts->{pointers}->{$old_pointer};
}
}
}
} elsif (!/^thread_done/) {
die "$.: Unknown line $_\n";
}
}
PrintFreeSlots($opts);
return $opts->{last_slot};
}
sub ProcessArgs($) {
my ($opts) = @_;
$opts->{print_definitions} = 0;
$opts->{ignore_missing_allocations} = 0;
$opts->{print_mainloop} = 0;
my @args = ();
while (scalar(@ARGV)) {
my $arg = pop(@ARGV);
if ($arg =~ /^-/) {
if ($arg eq "-d") {
$opts->{print_definitions} = 1;
} elsif ($arg eq "-i") {
$opts->{ignore_missing_allocations} = 1;
} elsif ($arg eq "-m") {
$opts->{print_mainloop} = 1;
} else {
print "Unknown option $arg\n";
PrintUsageAndExit();
}
} else {
unshift @args, $arg;
}
}
return @args;
}
my $opts = {};
my @args = ProcessArgs($opts);
if (scalar(@args) != 3) {
PrintUsageAndExit();
}
my $thread = $args[0];
my $struct_name = $args[1];
my $max_slot_name = $args[2];
PrintHeader();
if ($opts->{print_definitions}) {
PrintDefinitions();
}
if ($opts->{print_mainloop}) {
PrintMainloop();
}
print "static MallocEntry ${struct_name}[] = {\n";
my $total_slots = PrintEntries($thread, $opts->{ignore_missing_allocations});
print "};\n";
print "static constexpr size_t ${max_slot_name} = $total_slots;\n";