Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions experiments/test_multithreading_speedup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/bin/bash
#
# test_multithreading_speedup.sh
#
# Experiment script to verify that multithreading actually utilizes multiple CPU cores
# and provides performance benefits.
#
# Usage: ./test_multithreading_speedup.sh [build_dir] [config_file]
#
# This script:
# 1. Runs the benchmark in single-threaded mode
# 2. Runs the benchmark in multi-threaded mode with different thread counts
# 3. Compares the results and calculates speedup
#

set -e

# Default paths
BUILD_DIR="${1:-../build}"
CONFIG_FILE="${2:-../configs/default.json}"
EXECUTABLE="${BUILD_DIR}/NNets"

if [ ! -f "$EXECUTABLE" ]; then
echo "Error: Executable not found at $EXECUTABLE"
echo "Please build the project first: cd build && cmake .. && make"
exit 1
fi

if [ ! -f "$CONFIG_FILE" ]; then
echo "Error: Config file not found at $CONFIG_FILE"
exit 1
fi

echo "=============================================="
echo "Multithreading Speedup Experiment"
echo "=============================================="
echo "Executable: $EXECUTABLE"
echo "Config: $CONFIG_FILE"
echo "Available CPU cores: $(nproc)"
echo ""

# Function to extract training time from benchmark output
extract_training_time() {
grep "Training time:" | sed 's/.*Training time: \([0-9]*\) ms/\1/'
}

# Function to extract thread count from benchmark output
extract_thread_count() {
grep "Threads:" | sed 's/.*Threads: \([0-9]*\).*/\1/'
}

echo "Running single-threaded benchmark..."
SINGLE_OUTPUT=$("$EXECUTABLE" -c "$CONFIG_FILE" -b --single-thread 2>&1)
SINGLE_TIME=$(echo "$SINGLE_OUTPUT" | extract_training_time)
echo " Single-threaded training time: ${SINGLE_TIME} ms"
echo ""

echo "Running multi-threaded benchmarks..."

for THREADS in 2 4 $(nproc); do
MULTI_OUTPUT=$("$EXECUTABLE" -c "$CONFIG_FILE" -b -j "$THREADS" 2>&1)
MULTI_TIME=$(echo "$MULTI_OUTPUT" | extract_training_time)
ACTUAL_THREADS=$(echo "$MULTI_OUTPUT" | extract_thread_count)

if [ -n "$MULTI_TIME" ] && [ "$MULTI_TIME" -gt 0 ]; then
SPEEDUP=$(echo "scale=2; $SINGLE_TIME / $MULTI_TIME" | bc)
echo " ${ACTUAL_THREADS} threads: ${MULTI_TIME} ms (speedup: ${SPEEDUP}x)"
else
echo " ${THREADS} threads: Failed to get timing"
fi
done

echo ""
echo "=============================================="
echo "Experiment completed"
echo "=============================================="
49 changes: 24 additions & 25 deletions include/neuron_generation.h
Original file line number Diff line number Diff line change
Expand Up @@ -696,9 +696,11 @@ void SearchThreadFunc(
* между несколькими потоками. Каждый поток исследует
* случайные комбинации параметров нейронов независимо.
*
* Ключевая идея: общее количество итераций увеличивается
* пропорционально количеству потоков, что обеспечивает
* эквивалентное качество поиска при большей скорости.
* Стратегия распределения работы:
* - Общее количество итераций делится между потоками
* - Это обеспечивает приблизительно такое же качество поиска
* как однопоточная версия, но за меньшее время
* - Каждый поток имеет минимальное гарантированное количество итераций
*
* @return минимальная достигнутая ошибка, или big если не найдено
*/
Expand All @@ -708,33 +710,30 @@ float rndrod4_parallel() {
return rndrod4();
}

// Общее количество итераций для параллельного поиска
// Каждый поток получает count_max / NumThreads итераций
// Но общее количество итераций (сумма по всем потокам) равно count_max
// Общее количество итераций для однопоточного поиска
int count_max = Neirons * Receptors * 4;

// Для задач с малым и средним количеством нейронов параллелизация
// может привести к нестабильному качеству поиска из-за:
// 1. Независимости траекторий поиска между потоками
// 2. Меньшего количества итераций на поток при делении workload
// 3. Разных точек сходимости случайного поиска
//
// Используем однопоточную версию для обеспечения стабильного качества
// обучения на небольших и средних задачах (< 500 нейронов).
// Параллельный режим применяется только для крупных сетей, где
// объём вычислений достаточен для надёжного параллельного поиска.
//
// Порог: count_max = Neirons * Receptors * 4
// При Neirons = 500, Receptors = 20: count_max = 40000
// Используем порог 500000 чтобы гарантировать Neirons >= 6250 при Receptors=20
if (count_max < 500000) {
// Минимальное количество итераций на поток для качественного поиска.
// При слишком малом количестве итераций случайный поиск может
// не найти хорошее решение из-за недостаточного исследования
// пространства параметров. Значение 1000 обеспечивает достаточное
// покрытие пространства поиска на каждом потоке.
const int MIN_ITERATIONS_PER_THREAD = 1000;

// Вычисляем количество итераций на поток
// Общая работа делится между потоками, но каждый поток получает
// минимум MIN_ITERATIONS_PER_THREAD итераций
int base_iterations_per_thread = (count_max + NumThreads - 1) / NumThreads;
int iterations_per_thread = std::max(base_iterations_per_thread, MIN_ITERATIONS_PER_THREAD);

// Используем однопоточную версию если count_max слишком мал,
// чтобы параллелизация имела смысл. Порог: нужно достаточно
// итераций чтобы каждый поток мог эффективно работать.
// При count_max < 2000 накладные расходы на потоки превышают выгоду.
if (count_max < 2000) {
return rndrod4();
}

// Каждый поток выполняет часть итераций
// Для крупных сетей это обеспечивает значительное ускорение
int iterations_per_thread = (count_max + NumThreads - 1) / NumThreads;

// Создаём результаты для каждого потока
vector<ThreadSearchResult> results(NumThreads);

Expand Down