| Адрес | Значение (1 байт) |
|---|---|
| 0x0000 | ... |
| ... | ... |
| 0x1000 | 1 |
| 0x1001 | 2 |
| 0x1002 | 3 |
| 0x1003 | 4 |
| ... | ... |
| 0xffffffffff | ... |
// Просто хранит какой-то адрес
void* addr = 0x1000;
// Если указатель никуда не ссылается,
// надо использовать nullptr
void* invalid = nullptr;
// Размер указателя, например, 4 - это количество
// байт необходимых для размещения адреса
size_t size = sizeof(addr); // size == 4
// Теперь мы говорим компилятору как
// интерпретировать то, на что указывет
// указатель
char* charPtr = (char*) 0x1000;
// Разыменование - получение значения, находящегося
// по указанному адресу
char c = *charPtr; // c == 1
// & - взятие адреса, теперь в charPtrPtr находится
// адрес charPtr
char** charPtrPtr = &charPtr;
int* intPtr = (int*) addr;
int i = *intPtr; // i == 0x04030201 (little endian)
int* i1 = intPtr;
int* i2 = i1 + 2;
ptrdiff_t d1 = i2 - i1; // d1 == 2
char* c1 = (char*) i1;
char* c2 = (char*) i2;
ptrdiff_t d2 = c2 - c1; // d2 == 8T* + n -> T* + sizeof(T) * n
T* - n -> T* - sizeof(T) * n
C-cast использовать в С++ нельзя! Как надо приводить типы в С++ и надо ли вообще будет в другой лекции
| Знаковые | Беззнаковые |
|---|---|
| char | unsigned char |
| short | unsigned short |
| int | unsigned или unsigned int |
| long | unsigned long |
Стандарт не регламентирует размер типов
#include <cstdint>| Размер, бит | Тип |
|---|---|
| 8 | int8_t, int_fast8_t, int_least8_t |
| 16 | int16_t, int_fast16_t, int_least16_t |
| 32 | int32_t, int_fast32_t, int_least32_t |
| 64 | int64_t, int_fast64_t, int_least64_t |
Беззнаковая (unsigned) версия - добавление префикса u
#include <iostream>
#include <cstdint>
int global = 0;
int main()
{
int* heap = (int*) malloc(sizeof(int));
std::cout << std::hex << (uint64_t) main << '\n';
std::cout << std::hex << (uint64_t) &global << '\n';
std::cout << std::hex << (uint64_t) heap << '\n';
std::cout << std::hex << (uint64_t) &heap << '\n';
char c;
std::cin >> c;
return 0;
}g++ -O0 mem.cpp -o mem --std=c++11
./mem
400986
6022b4
18adc20
7ffd5591e7d0
ps ax | grep mem
00400000-00401000 r-xp 00000000 08:01 2362492
/home/mt/work/tmp/mem
00601000-00602000 r--p 00001000 08:01 2362492
/home/mt/work/tmp/mem
00602000-00603000 rw-p 00002000 08:01 2362492
/home/mt/work/tmp/mem
0189c000-018ce000 rw-p 00000000 00:00 0
[heap]
7f66aaa53000-7f66aabc5000 r-xp 00000000 08:01 6826866
/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f66aadc5000-7f66aadcf000 r--p 00172000 08:01 6826866
/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7f66aadcf000-7f66aadd1000 rw-p 0017c000 08:01 6826866
/usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21
7ffd55900000-7ffd55921000 rw-p 00000000 00:00 0
[stack]
7ffd55952000-7ffd55954000 r--p 00000000 00:00 0
[vvar]
7ffd55954000-7ffd55956000 r-xp 00000000 00:00 0
[vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0
[vsyscall]
Память разбита на сегменты:
- кода (CS)
- данных (DS)
- стека (SS)
Регистр сегмента (CS, DS, SS) указывают на дескриптор.
Для инструкций и стека на смещение в сегменте указывает регистр:
- кода (EIP)
- стека (ESP)
Линейный адрес - это сумма базового адреса сегмента и смещения.
Segment limit (20 bit) - размер сегмента, 55-й бит G определяет гранулярность размера:
- байты, если 0
- страницы, если 1 (размер страницы обычно 4Кб)
Бит 41-43:
- 000 - сегмент данных, только чтение
- 001 - сегмент данных, чтение и запись
- 010 - сегмент стека, только чтение
- 011 - сегмент стека, чтение и запись
- 100 - сегмент кода, только выполнение
- 101- сегмент кода, чтение и выполнение
- Память делится на страницы
- Страница может находится в оперативной памяти или на внешнем носителе
- Трансляция из физического адреса в виртуальный и обратно выполняется через специальные таблицы: PGD (Page Global Directory), PMD (Page Middle Directory) и PTE (Page Table Entry). В PTE хранятся физические адреса страниц
- Для ускорения трансляции адресов процессор хранит в кеше таблицу TLB (Translation lookaside buffer)
- Если обращение к памяти не может быть оттранслировано через TLB, процессор обращается к таблицам страниц и пытается загрузить PTE оттуда в TLB. Если загрузка не удалась, процессор вызывает прерывание Page Fault
- Обработчик прерывания Page Fault находится в подсистеме виртуальной памяти ядра ОС и может загрузить требуемую страницу с внешнего носителя в оперативную память
1 такт = 1 / частота процессора
1 / 3 GHz = 0.3 ns
0.3 ns
L1 cache reference 0.5 ns
Branch mispredict 5 ns
Неудачный if ()
L2 cache reference 7 ns
Mutex lock/unlock 25 ns
Main memory reference 100 ns
Кроме задержки (latency) есть понятие пропускной способности (throughput, bandwidth). В случае чтения из RAM - 10-50 Gb/sec
Compress 1K bytes with Zippy 3,000 ns
Send 1K bytes over 1 Gbps network 10,000 ns
Read 4K randomly from SSD 150,000 ns
Read 1 MB sequentially from memory 250,000 ns
Round trip within same datacenter 500,000 ns
Read 1 MB sequentially from SSD 1,000,000 ns
HDD seek 10,000,000 ns
Read 1 MB sequentially from HDD 20,000,000 ns
Send packet CA->Netherlands->CA 150,000,000 ns
Источник: https://gist.github.com/jboner/2841832
- Стараться укладывать данные в кеш
- Минимизировать скачки по памяти
- В условиях основной веткой делать ветку которая выполняется с большей вероятностью
Характеризуются тремя понятиями:
- Время жизни
Продолжительность хранения данных в памяти
- Область видимости
Части кода из которых можно получить доступ к данным
- Связывание (linkage)
Если к данным можно обратиться из другой единицы трансляции — связывание внешнее (external), иначе связывание внутреннее (internal)
| Время жизни | Область видимости | Связывание |
|---|---|---|
| Автоматическое (блок) | Блок | Отсутствует |
{
int i = 5;
}
if (true)
{
register int j = 3;
}
for (int k = 0; k < 7; ++k)
{
}| Время жизни | Область видимости | Связывание |
|---|---|---|
| Статическое | Блок | Отсутствует |
void foo()
{
static int j = 3;
}Инициализируется при первом обращении
| Время жизни | Область видимости | Связывание |
|---|---|---|
| Статическое | Файл | Внутреннее |
static int i = 5;Инициализируется до входа в main
| Время жизни | Область видимости | Связывание |
|---|---|---|
| Статическое | Файл | Внешнее |
// *.cpp
int i = 0;// *.h
extern int i;int i = 5;
std::string name;
char data[5];Выделение памяти на стеке очень быстрая, но стек не резиновый
int* i = (int*) malloc(sizeof(int));
std::string* name = new std::string();
char* data = new char[5];
...
free(i);
delete(name);
delete[] data;Память в куче выделяют new и malloc, есть сторонние менеджеры памяти.
Основное:
- new то же, что и malloc, только дополнительно вызывает конструкторы
- Внутри malloc есть буфер, если в буфере есть место, ваш вызов может выполниться быстро
- Если памяти в буфере нет, будет запрошена память у ОС (sbrk, VirtualAlloc) - это дорого
- ОС выделяет память страницами от 4Кб, а может быть и все 2Мб
- Стандартные аллокаторы универсальные, то есть должны быть потокобезопасны, быть одинаково эффективны для блоков разной длины, и 10 байт и 100Мб. Плата за универсальность - быстродействие
#include <cstdlib>
int main()
{
int* data = (int*) malloc(1024);
return 0;
}valgrind ./mem
==117392== Memcheck, a memory error detector
==117392== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==117392== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==117392== Command: ./mem
==117392==
==117392==
==117392== HEAP SUMMARY:
==117392== in use at exit: 1,024 bytes in 1 blocks
==117392== total heap usage: 1 allocs, 0 frees, 1,024 bytes allocated
==117392==
==117392== LEAK SUMMARY:
==117392== definitely lost: 1,024 bytes in 1 blocks
==117392== indirectly lost: 0 bytes in 0 blocks
==117392== possibly lost: 0 bytes in 0 blocks
==117392== still reachable: 0 bytes in 0 blocks
==117392== suppressed: 0 bytes in 0 blocks
==117392== Rerun with --leak-check=full to see details of leaked memory
==117392==
==117392== For counts of detected and suppressed errors, rerun with: -v
==117392== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
static const int i = 5;
static std::string name;
extern char data[5];Если не удастся разместить блок глобальной памяти, то программа даже не запустится
T array[maxColumns];
T value = array[x];Значение в квадратных скобках должно быть известно на этапе компиляции, увы
int data[] = { 1, 2, 3 };
int i = data[2];Фактически - это вычисление смещения:
ptr = data;
ptr = ptr + 2 * sizeof(int);
i = *ptr;Массив - непрерывный блок байт в памяти, sizeof(data) вернет размер массива в байтах (не элементах!). Размер массива в элементах можно вычислить: sizeof(data) / sizeof(data[0])
int* data = new int[10];
int i = data[2];
delete[] data;int i[] = { 1, 2, 3 };
int* j = i;
using array = int*;
array k = (array) j;T array[maxRows][maxColumns];
T value = array[y][x];int data[][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
int i = data[2][1];Фактически:
ptr = data;
ptr = ptr + maxColumns * sizeof(int) * 2 + 1;
i = *ptr;int i[][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
int* j = (int*) i;
using matrix = int(*)[2];
matrix k = (matrix) j;- Измерений должно быть много
- Одному прогону верить нельзя
- Компилятор оптимизирует, надо ему мешать
- Перед тестами надо греться
int main()
{
Timer t;
for (int i = 0; i < 100 * 1000 * 1000; ++i)
int a = i / 3;
return 0;
}int main()
{
Timer t;
for (int i = 0; i < 100 * 1000 * 1000; ++i)
volatile int a = i / 3;
return 0;
}#include <chrono>
#include <iostream>
class Timer
{
using clock_t = std::chrono::high_resolution_clock;
using microseconds = std::chrono::microseconds;
public:
Timer()
: start_(clock_t::now())
{
}
~Timer()
{
const auto finish = clock_t::now();
const auto us =
std::chrono::duration_cast<microseconds>
(finish - start_).count();
std::cout << us << " us" << std::endl;
}
private:
const clock_t::time_point start_;
};Написать две программы, суммирующие элементы двумерного массива (матрицы) целых чисел. Одна программа суммирует по столбцам, вторая - по строкам. Измерить время работы в обоих случаях, сравнить результаты. Для замеров можно использовать класс Timer. В репозитории в директории homework создать директорию со своей фамилией, внутри этой директории создать директорию 01, файлы с решением положить внутрь. Программу компилировать с влюченной оптимизацией, например, так:
g++ sum_by_rows.cpp -o sum_by_rows --std=c++11 -O2
Оба решения можно запустить через valgrind и посмотреть количество промахов в кеш:
valgrind --tool=cachegrind your_program
Подумать.
EOF


