mariadb/storage/heap/hp_block.c
Monty 52c29f3bdc MDEV-35469 Heap tables are calling mallocs to often
Heap tables are allocated blocks to store rows according to
my_default_record_cache (mapped to the server global variable
 read_buffer_size).
This causes performance issues when the record length is big
(> 1000 bytes) and the my_default_record_cache is small.

Changed to instead split the default heap allocation to 1/16 of the
allowed space and not use my_default_record_cache anymore when creating
the heap. The allocation is also aligned to be just under a power of 2.

For some test that I have been running, which was using record length=633,
the speed of the query doubled thanks to this change.

Other things:
- Fixed calculation of max_records passed to hp_create() to take
  into account padding between records.
- Updated calculation of memory needed by heap tables. Before we
  did not take into account internal structures needed to access rows.
- Changed block sized for memory_table from 1 to 16384 to get less
  fragmentation. This also avoids a problem where we need 1K
  to manage index and row storage which was not counted for before.
- Moved heap memory usage to a separate test for 32 bit.
- Allocate all data blocks in heap in powers of 2. Change reported
  memory usage for heap to reflect this.

Reviewed-by: Sergei Golubchik <serg@mariadb.org>
2025-01-05 16:40:11 +02:00

157 lines
4.8 KiB
C

/* Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
/* functions on blocks; Keys and records are saved in blocks */
#include "heapdef.h"
/*
Find record according to record-position.
The record is located by factoring position number pos into (p_0, p_1, ...)
such that
pos = SUM_i(block->level_info[i].records_under_level * p_i)
{p_0, p_1, ...} serve as indexes to descend the blocks tree.
*/
uchar *hp_find_block(HP_BLOCK *block, ulong pos)
{
reg1 int i;
reg3 HP_PTRS *ptr; /* block base ptr */
for (i=block->levels-1, ptr=block->root ; i > 0 ; i--)
{
ptr=(HP_PTRS*)ptr->blocks[pos/block->level_info[i].records_under_level];
pos%=block->level_info[i].records_under_level;
}
return (uchar*) ptr+ pos*block->recbuffer;
}
/*
Get one new block-of-records. Alloc ptr to block if needed
SYNOPSIS
hp_get_new_block()
info heap handle
block HP_BLOCK tree-like block
alloc_length OUT Amount of memory allocated from the heap
Interrupts are stopped to allow ha_panic in interrupts
RETURN
0 OK
1 Out of memory
*/
int hp_get_new_block(HP_SHARE *info, HP_BLOCK *block, size_t *alloc_length)
{
reg1 uint i,j;
HP_PTRS *root;
for (i=0 ; i < block->levels ; i++)
if (block->level_info[i].free_ptrs_in_block)
break;
/*
Allocate space for leaf block (data) plus space for upper level blocks
up to first level that has a free slot to put the pointer.
If this is a new level, we have to allocate pointers to all future
lower levels.
For example, for level 0, we allocate data for X rows.
When level 0 is full, we allocate data for HP_PTRS_IN_NOD + X rows.
Next time we allocate data for X rows.
When level 1 is full, we allocate data for HP_PTRS_IN_NOD at level 2 and 1
+ X rows at level 0.
*/
*alloc_length= (sizeof(HP_PTRS) * ((i == block->levels) ? i : i - 1) +
(ulonglong)block->records_in_block * block->recbuffer);
/* Alloc in blocks of powers of 2 */
*alloc_length= MY_MAX(*alloc_length, block->alloc_size);
if (!(root=(HP_PTRS*) my_malloc(hp_key_memory_HP_PTRS, *alloc_length,
MYF(MY_WME |
(info->internal ?
MY_THREAD_SPECIFIC : 0)))))
return 1;
if (i == 0)
{
block->levels=1;
block->root=block->level_info[0].last_blocks=root;
}
else
{
if ((uint) i == block->levels)
{
/* Adding a new level on top of the existing ones. */
block->levels=i+1;
/*
Use first allocated HP_PTRS as a top-level block. Put the current
block tree into the first slot of a new top-level block.
*/
block->level_info[i].free_ptrs_in_block=HP_PTRS_IN_NOD-1;
((HP_PTRS**) root)[0]= block->root;
block->root=block->level_info[i].last_blocks= root++;
}
/* Occupy the free slot we've found at level i */
block->level_info[i].last_blocks->
blocks[HP_PTRS_IN_NOD - block->level_info[i].free_ptrs_in_block--]=
(uchar*) root;
/* Add a block subtree with each node having one left-most child */
for (j=i-1 ; j >0 ; j--)
{
block->level_info[j].last_blocks= root++;
block->level_info[j].last_blocks->blocks[0]=(uchar*) root;
block->level_info[j].free_ptrs_in_block=HP_PTRS_IN_NOD-1;
}
/*
root now points to last (block->records_in_block* block->recbuffer)
allocated bytes. Use it as a leaf block.
*/
block->level_info[0].last_blocks= root;
}
return 0;
}
/* free all blocks under level */
uchar *hp_free_level(HP_BLOCK *block, uint level, HP_PTRS *pos, uchar *last_pos)
{
int i,max_pos;
uchar *next_ptr;
if (level == 1)
next_ptr=(uchar*) pos+block->recbuffer;
else
{
max_pos= (block->level_info[level-1].last_blocks == pos) ?
HP_PTRS_IN_NOD - block->level_info[level-1].free_ptrs_in_block :
HP_PTRS_IN_NOD;
next_ptr=(uchar*) (pos+1);
for (i=0 ; i < max_pos ; i++)
next_ptr=hp_free_level(block,level-1,
(HP_PTRS*) pos->blocks[i],next_ptr);
}
if ((uchar*) pos != last_pos)
{
my_free(pos);
return last_pos;
}
return next_ptr; /* next memory position */
}