В настоящее время я работаю над микропроцессором с небольшим объемом оперативной памяти (128 МБ). Я запускаю несколько потоков выполнения для анализа производительности, но суть проблемы заключается в хранении больших объемов данных (поплавков и целых чисел) в динамическом пространстве, чтобы уменьшить возможность копирования/распределения данных, к которым склонны такие структуры данных, как векторы. полагаться в исполнении.
Я решил протестировать несколько возможных схем, как решить эту проблему в CLion. Я написал следующий код:
#include <cstdlib>
#include <cstdio>
int initial_capacity = 3;
int current_capacity = 3;
int current_count = 0;
int*** initializeArray(){
// initialize triple array space in memory
int*** arr;
/*
3 items -> 3 data sets -> each data set starts with 3 spaces
a "int arr[3][3][3]" but dynamically allocated
*/
arr = (int***) calloc(current_capacity, sizeof(int**));
for(int i = 0; i < current_capacity; i++){
arr[i] = (int**) calloc(current_capacity, sizeof(int*));
for(int j =0; j < current_capacity; j++){
arr[i][j] = (int*) calloc(current_capacity,sizeof(int));
}
}
return arr;
}
void resizeArray(int *** arr){
auto max = (double) current_capacity;
double percentage = current_count/max;
if (percentage >= 0.5){
for (int i = 0; i < initial_capacity; ++i) {
for (int j = 0; j < initial_capacity; ++j) {
current_capacity*=2;
arr[i][j] = (int*) realloc(arr[i][j],(current_capacity)*sizeof(int));
}
}
}
}
void deleteArrays(int *** arr){
for (int i = 0; i < initial_capacity; ++i) {
for (int j = 0; j < initial_capacity; ++j) {
for (int k = 0; k < current_count; ++k) {
arr[i][j][k] = NULL;
}
}
}
printf("Releasing allocated arrays 1\n");
for (int i = 0; i < initial_capacity; ++i) {
for (int j = 0; j < initial_capacity; ++j) {
arr[i][j] = (int*) realloc(arr[i][j],sizeof(int));
free(arr[i][j]);
}
}
printf("Releasing allocated arrays 2\n");
for (int i = 0; i < initial_capacity; ++i) {
free(arr[i]);
}
printf("Releasing allocated arrays 3\n");
free(arr);
}
void printArrays(int *** arr){
for (int i = 0; i < 3; ++i) {
printf("Array[%d]\n", i);
for (int j = 0; j < 3; ++j) {
printf("Array[%d][%d]\nElements: ", i, j);
for (int k = 0; k < current_count; ++k) {
printf(" %d", arr[i][j][k]);
}
printf("\n");
}
}
printf("\n");
}
int main() {
int *** generated= initializeArray();
int count = 0;
while (count < 21){
if (count % 3 == 0) printArrays(generated);
//verify
resizeArray(generated);
generated[0][0][current_count] = rand();
generated[0][1][current_count] = rand();
generated[0][2][current_count] = rand();
generated[1][0][current_count] = rand();
generated[1][1][current_count] = rand();
generated[1][2][current_count] = rand();
generated[2][0][current_count] = rand();
generated[2][1][current_count] = rand();
generated[2][2][current_count] = rand();
current_count++;
count++;
}
/* some operation with data collected */
deleteArrays(generated);
printf("Finished deleting dynamic arrays");
return 0;
}
Я попытался сгенерировать тройной массив, чтобы сохранить тот же объем информации в последнем подмножестве, в то время как его размер и общая сумма продолжали увеличиваться. Однако процедура удаления массива для завершения динамического аспекта процесса всегда приводит к завершению процесса с кодом выхода -1073740940 (0xC0000374), что является ошибкой повреждения кучи. Я не очень хорошо знаком с этим, и любой отзыв об этом был бы полезен.
Не спрашивайте одновременно о C и C++, за исключением вопросов, касающихся различий или взаимодействия между языками. Поскольку в коде есть операторы #include
, которые не будут работать в C, я удаляю тег C.
@SamVarshavchik Я просмотрел некоторые возможные операторы, но обычно мне удавалось использовать только те, которые относятся к первому элементу адреса, и во всех случаях он возвращает допустимый элемент. Не уверен, как проверить в остальных выделенных пространствах
Действительно ли необходимо иметь трехмерный массив с тремя разными вызовами calloc? Есть ли веская причина не использовать только одномерный массив, а все остальное делать со сдвигом индекса? Иногда наличие одного большого блока распределения значительно облегчает вашу жизнь. Также вы можете воспользоваться некоторыми преимуществами локальности кеша, в зависимости от микропроцессора :)
@hakononakani Это концепция, о которой я не знал. Обычно я управлял только n-массивами в итеративной последовательности. Я посмотрю на это!
Подпрограмма resizeArray
удваивает current_capacity
с current_capacity*=2;
на каждой итерации своего внутреннего цикла. При первом вызове это приводит к тому, что arr[0][0]
устанавливается на память для 6 int
, в то время как циклы продолжаются и в конечном итоге оставляют current_capacity
равным 1536. Для остальной части выполнения программы resizeArray
никогда не выделяет больше памяти из-за того, что что его порог относительно current_capacity
никогда не достигается.
Тем временем подпрограмма main
продолжает записывать все больше и больше элементов в generated[0][0]
, увеличивая current_count
до 20 и, следовательно, записывая за пределы выделенной памяти.
Эту ошибку можно исправить, переместив current_capacity*=2;
из циклов внутрь блока «тогда» if (percentage >= 0.5)
.
Также обратите внимание, что метод реализации многомерных массивов в виде указателей на указатели или указателей на указатели на указатели неэффективен во времени и пространстве. «Погоня за указателем» плоха для спекулятивного выполнения инструкций процессором. Для многомерных массивов фиксированного размера это не используется в коде производственного качества. В этом случае, когда требуется переменное последнее измерение, выбор немного менее очевиден, но все же может быть предпочтительнее использовать одно непрерывное выделение памяти и использовать индексную арифметику для вычисления местоположений в массиве, а не поиск по указателю. (C делает это довольно просто с помощью массивов переменной длины, хотя их поддержка необязательна. Реализации C++ могут предоставлять массивы переменной длины в качестве расширения, но необходимые арифметические действия несложны и могут быть выполнены с помощью вспомогательных функций или классов.)
Вы пытались добавить операторы отладки для печати необработанных указателей каждого выделенного/перераспределенного/удаленного массива? Исходя из этого, вы должны быть в состоянии обнаружить любые плохие удаления. Если все в порядке, используйте инструмент статического анализа памяти, чтобы найти повреждение памяти.