diff --git a/DD_rapport.pdf b/DD_rapport.pdf index 860e438..e060573 100644 Binary files a/DD_rapport.pdf and b/DD_rapport.pdf differ diff --git a/Main.ipynb b/Main.ipynb index 24502a0..d07341d 100644 --- a/Main.ipynb +++ b/Main.ipynb @@ -15,7 +15,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "78fdf40a", "metadata": { "ExecuteTime": { @@ -29,6 +29,7 @@ "import redis\n", "import time\n", "import chess\n", + "import chess.polyglot\n", "import chess.syzygy\n", "import random\n", "import numpy as np\n", @@ -54,7 +55,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "e6058974", "metadata": { "ExecuteTime": { @@ -62,53 +63,7 @@ "start_time": "2025-12-06T09:22:06.592819Z" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Configs ok: {'KNvK', 'KRPvKP', 'KBvK', 'KBNvK', 'KRBvKP', 'KQvK', 'KQvKP', 'KRvK', 'KPvKP', 'KPvK', 'KRvKB'}\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "
. . . . k . . .\n",
-       ". . . . . . . .\n",
-       ". . . n . . . .\n",
-       ". . . . . . . .\n",
-       ". . . . . . . p\n",
-       ". P . . . . . .\n",
-       ". . . . B . . .\n",
-       ". . . . K . . .
" - ], - "text/plain": [ - "Board('4k3/8/3n4/8/7p/1P6/4B3/4K3 b - - 0 1')" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Récupération des positions de la tablebase KBNvK\n", - "Récupération des positions de la tablebase KPvK\n", - "Récupération des positions de la tablebase KPvKP\n", - "Récupération des positions de la tablebase KQvK\n", - "Récupération des positions de la tablebase KQvKP\n", - "Récupération des positions de la tablebase KRBvKP\n", - "Récupération des positions de la tablebase KRPvKP\n", - "Récupération des positions de la tablebase KRvK\n", - "Récupération des positions de la tablebase KRvKB\n", - "Récupération des positions de la tablebase KBvK\n", - "Récupération des positions de la tablebase KNvK\n", - "Total positions récupérées: 55000\n", - "Ajoute de toutes les positions des tablebases dans Redis terminé!\n" - ] - } - ], + "outputs": [], "source": [ "def get_250_random_position(tablebases, config):\n", " positions = []\n", @@ -160,6 +115,7 @@ "def add_tablebase_to_redis():\n", " #Connect to Redis server\n", " redis_server = redis.Redis(host='localhost', port=6379, db=0)\n", + " redis_server.flushdb() \n", " \n", " tablebases_path = \"tablebases/\" #Chemin vers les tables bases Syzygy\n", "\n", @@ -185,10 +141,29 @@ " all_positions.extend(position) #Ajout des positions générées à la liste globale\n", " \n", " print(f\"Total positions récupérées: {len(all_positions)}\")\n", - " print(\"Ajoute de toutes les positions des tablebases dans Redis terminé!\")\n", + " \n", + " count = 0\n", + " for position in all_positions:\n", + " try:\n", + " fen = position.fen()\n", + " wdl = tablebases.probe_wdl(position)\n", + " dtz = tablebases.probe_dtz(position)\n", + " pieces = len(position.piece_map())\n", + " \n", + " redis_server.hset(fen, mapping={\n", + " \"wdl\": wdl,\n", + " \"dtz\": dtz,\n", + " \"pieces\": pieces\n", + " })\n", + " count += 1\n", + " except:\n", + " continue\n", + " \n", + " print(f\"✓ {count} positions ajoutées dans Redis DB 0\")\n", + " print(\"Ajout de toutes les positions des tablebases dans Redis terminé!\")\n", "\n", "if __name__ == \"__main__\":\n", - " add_tablebase_to_redis() " + " add_tablebase_to_redis()" ] }, { @@ -207,7 +182,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "8d49b928-7522-4727-b6d8-224e2bd3a1b7", "metadata": { "ExecuteTime": { @@ -275,103 +250,13 @@ " }\n" ] }, - { - "cell_type": "code", - "execution_count": 4, - "id": "0d955b0d-dc80-4518-954b-7c2df295adca", - "metadata": { - "ExecuteTime": { - "end_time": "2025-12-06T10:18:18.501308Z", - "start_time": "2025-12-06T10:18:18.393765Z" - } - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "BENCHMARK SYZYGY\n", - "BENCHMARK REDIS\n", - "Redis latency array length: 55000\n", - "Syzygy latency array length: 50113\n", - " test statistic p-value \\\n", - "0 paired t-test 91.572499 0.0 \n", - "1 KS-test 0.697863 0.0 \n", - "\n", - " interpretation \n", - "0 H0: mean latency equal (Redis vs Syzygy) \n", - "1 H0: same latency distribution (Redis vs Syzygy) \n" - ] - } - ], - "source": [ - "def statistical_tests(bench_redis, bench_syzygy):\n", - " redis_lat = np.array(bench_redis[\"latency\"], dtype=np.float64)\n", - " syz_lat = np.array(bench_syzygy[\"latency\"], dtype=np.float64)\n", - " \n", - " min_len = min(len(redis_lat), len(syz_lat))\n", - " redis_lat = redis_lat[:min_len]\n", - " syz_lat = syz_lat[:min_len]\n", - "\n", - " # PAIRED T-Test (Comparaison median)\n", - " t_stat, t_p = stats.ttest_rel(syz_lat, redis_lat)\n", - "\n", - " # KS Test (Comparaison distribution)\n", - " ks_stat, ks_p = stats.ks_2samp(syz_lat, redis_lat)\n", - "\n", - " df_stats = pd.DataFrame({\n", - " \"test\": [\"paired t-test\", \"KS-test\"],\n", - " \"statistic\": [t_stat, ks_stat],\n", - " \"p-value\": [t_p, ks_p],\n", - " \"interpretation\": [\n", - " \"H0: mean latency equal (Redis vs Syzygy)\",\n", - " \"H0: same latency distribution (Redis vs Syzygy)\"\n", - " ]\n", - " })\n", - "\n", - " print(df_stats)\n", - " return df_stats\n", - "\n", - "\n", - "def run_experiment_test_latency():\n", - " redis_server = redis.Redis(host=\"localhost\", port=6379, db=0)\n", - " tablebases = chess.syzygy.open_tablebase(\"tablebases/\")\n", - "\n", - " configs = [\"KBNvK\",\"KPvK\",\"KPvKP\",\"KQvK\",\"KQvKP\",\"KRBvKP\",\"KRPvKP\",\"KRvK\",\"KRvKB\",\"KBvK\",\"KNvK\"]\n", - "\n", - " all_positions = []\n", - " for config in configs:\n", - " all_positions += get_250_random_position(tablebases, config)\n", - "\n", - " # Benchmark Syzygy\n", - " bench_syzygy = benchmark_syzygy(tablebases, all_positions)\n", - " print(\"BENCHMARK SYZYGY\")\n", - " #print(bench_syzygy)\n", - "\n", - " # Benchmark Redis\n", - " bench_redis = benchmark_redis(redis_server, all_positions)\n", - " print(\"BENCHMARK REDIS\")\n", - " #print(bench_redis)\n", - "\n", - " # Length info\n", - " print(f\"Redis latency array length: {len(bench_redis['latency'])}\")\n", - " print(f\"Syzygy latency array length: {len(bench_syzygy['latency'])}\")\n", - "\n", - " return {\n", - " \"bench_syzygy\": bench_syzygy,\n", - " \"bench_redis\": bench_redis\n", - " }\n", - "\n", - "if __name__ == \"__main__\":\n", - " results = run_experiment_test_latency()\n", - " statistical_tests(results[\"bench_redis\"], results[\"bench_syzygy\"])" - ] - }, { "cell_type": "markdown", "id": "37b01080", "metadata": {}, "source": [ + "# Test de l'hypothèse 1\n", + "\n", "Cette partie teste les accès Redis, afin de savoir si le nombre de pièces de la configuration importe sur le temps d'accès : " ] }, @@ -533,19 +418,138 @@ "\"\"\"" ] }, + { + "cell_type": "markdown", + "id": "48b2de71", + "metadata": {}, + "source": [ + "# Test de l'hypothèse 2\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d955b0d-dc80-4518-954b-7c2df295adca", + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-06T10:18:18.501308Z", + "start_time": "2025-12-06T10:18:18.393765Z" + } + }, + "outputs": [], + "source": [ + "def statistical_tests(bench_redis, bench_syzygy):\n", + " redis_lat = np.array(bench_redis[\"latency\"], dtype=np.float64)\n", + " syz_lat = np.array(bench_syzygy[\"latency\"], dtype=np.float64)\n", + " \n", + " min_len = min(len(redis_lat), len(syz_lat))\n", + " redis_lat = redis_lat[:min_len]\n", + " syz_lat = syz_lat[:min_len]\n", + "\n", + " # PAIRED T-Test (Comparaison median)\n", + " t_stat, t_p = stats.ttest_rel(syz_lat, redis_lat)\n", + "\n", + " # KS Test (Comparaison distribution)\n", + " ks_stat, ks_p = stats.ks_2samp(syz_lat, redis_lat)\n", + "\n", + " df_stats = pd.DataFrame({\n", + " \"test\": [\"paired t-test\", \"KS-test\"],\n", + " \"statistic\": [t_stat, ks_stat],\n", + " \"p-value\": [t_p, ks_p],\n", + " \"interpretation\": [\n", + " \"H0: mean latency equal (Redis vs Syzygy)\",\n", + " \"H0: same latency distribution (Redis vs Syzygy)\"\n", + " ]\n", + " })\n", + "\n", + " print(df_stats)\n", + " return df_stats\n", + "\n", + "\n", + "def run_experiment_test_latency():\n", + " redis_server = redis.Redis(host=\"localhost\", port=6379, db=0)\n", + " tablebases = chess.syzygy.open_tablebase(\"tablebases/\")\n", + "\n", + " configs = [\"KBNvK\",\"KPvK\",\"KPvKP\",\"KQvK\",\"KQvKP\",\"KRBvKP\",\"KRPvKP\",\"KRvK\",\"KRvKB\",\"KBvK\",\"KNvK\"]\n", + "\n", + " all_positions = []\n", + " for config in configs:\n", + " all_positions += get_250_random_position(tablebases, config)\n", + "\n", + " # Benchmark Syzygy\n", + " bench_syzygy = benchmark_syzygy(tablebases, all_positions)\n", + " print(\"BENCHMARK SYZYGY\")\n", + " print(bench_syzygy)\n", + "\n", + " # Benchmark Redis\n", + " bench_redis = benchmark_redis(redis_server, all_positions)\n", + " print(\"BENCHMARK REDIS\")\n", + " print(bench_redis)\n", + "\n", + " # Length info\n", + " print(f\"Redis latency array length: {len(bench_redis['latency'])}\")\n", + " print(f\"Syzygy latency array length: {len(bench_syzygy['latency'])}\")\n", + "\n", + " return {\n", + " \"bench_syzygy\": bench_syzygy,\n", + " \"bench_redis\": bench_redis\n", + " }\n", + "\n", + "if __name__ == \"__main__\":\n", + " results = run_experiment_test_latency()\n", + " statistical_tests(results[\"bench_redis\"], results[\"bench_syzygy\"])" + ] + }, { "cell_type": "markdown", "id": "3d0daed3", "metadata": {}, "source": [ - "# Partie Test 3\n", + "# Test de l'hypothèse 3\n", "\n", "Ici nous allons faire des tests sur les différentes structures de données possible pour stocker les positions.\n", - "Nous allons dans un premier temps peupler Redis avec la version enrichie des positions (c'est à dire avec la FEN, le wdl, le dtz et le nombre de pièces).\n", + "Nous allons dans un premier temps peupler Redis avec la version enrichie des positions (c'est à dire avec la FEN, le wdl, le dtz et le nombre de pièces) que nous allons comparer à Zobrit hash qui est la fonction de haschage de python chess.\n", "Ensuite nous allons créer le benchmark puis faire la comparaison.\n", "\n" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "53baf548", + "metadata": {}, + "outputs": [], + "source": [ + "def benchmark_redis_zobrist(redis_serveur, pos):\n", + " latency = []\n", + " start_time_all = time.perf_counter() \n", + " \n", + " for board in pos:\n", + " key = str(chess.polyglot.zobrist_hash(board))\n", + " start_individual = time.perf_counter()\n", + " result = redis_serveur.hgetall(key)\n", + " latency.append(time.perf_counter() - start_individual)\n", + " \n", + " total_time_all = time.perf_counter() - start_time_all\n", + " latency = np.array(latency)\n", + " \n", + " return {\n", + " \"count\": len(pos),\n", + " \"average_latency\": np.mean(latency),\n", + " \"p50\": np.percentile(latency, 50),\n", + " \"p90\": np.percentile(latency, 90),\n", + " \"p95\": np.percentile(latency, 95),\n", + " \"p99\": np.percentile(latency, 99),\n", + " \"std\": np.std(latency),\n", + " \"min\": np.min(latency),\n", + " \"max\": np.max(latency),\n", + " \"total_time\": total_time_all,\n", + " \"latency\": latency,\n", + " \"rps\": len(latency) / total_time_all,\n", + " }" + ] + }, { "cell_type": "code", "execution_count": null, @@ -557,7 +561,7 @@ " positions = []\n", " tries = 0\n", "\n", - " while len(positions) < 10000 and tries < 500:\n", + " while len(positions) < 10000 and tries < 5000:\n", " board = generate_board_from_config(config)\n", " positions.append(board)\n", " tries += 1\n", @@ -600,12 +604,11 @@ "\n", " return board\n", "\n", - "def add_tablebase_enrichie_to_redis():\n", + "def add_tablebase_zobrist_to_redis():\n", " #Connect to Redis server\n", - " redis_server = redis.Redis(host='localhost', port=6379, db=2)#2 car 1 est utilisé plus bas\n", + " redis_server = redis.Redis(host='localhost', port=6379, db=3)\n", " \n", " tablebases_path = \"tablebases/\" #Chemin vers les tables bases Syzygy\n", - "\n", " tablebases = chess.syzygy.open_tablebase(tablebases_path)\n", "\n", " available_tables = set()\n", @@ -628,58 +631,118 @@ " \n", " for position in all_positions:\n", " try:\n", - " fen = position.fen()\n", + " zobrist_key = str(chess.polyglot.zobrist_hash(position))\n", " wdl = tablebases.probe_wdl(position)\n", " dtz = tablebases.probe_dtz(position)\n", " pieces = len(position.piece_map())\n", " \n", - " redis_server.hset(fen, mapping={\n", + " redis_server.hset(zobrist_key, mapping={\n", " \"wdl\": wdl,\n", " \"dtz\": dtz,\n", " \"pieces\": pieces\n", " })\n", " except:\n", " continue\n", - " print(\"Ajoute de toutes les positions enrichies des tablebases dans Redis terminé!\")\n", + " print(\"Ajoute de toutes les positions hashées des tablebases dans Redis terminé!\")\n", "\n", "if __name__ == \"__main__\":\n", - " add_tablebase_enrichie_to_redis()" + " add_tablebase_zobrist_to_redis()" ] }, { "cell_type": "code", - "execution_count": 5, - "id": "53baf548", + "execution_count": null, + "id": "973deef0", "metadata": {}, "outputs": [], "source": [ - "def benchmark_redis_enrichie(redis_serveur, pos):\n", - " latency = []\n", - " start_time_all = time.perf_counter() \n", + "def statistical_tests(bench_fen, bench_zobrist):\n", + " from scipy import stats\n", + " import pandas as pd\n", " \n", - " for board in pos:\n", - " fen = board.fen()\n", - " start_individual = time.perf_counter()\n", - " result = redis_serveur.hgetall(fen)\n", - " latency.append(time.perf_counter() - start_individual)\n", + " fen_lat = np.array(bench_fen[\"latency\"], dtype=np.float64)\n", + " zobrist_lat = np.array(bench_zobrist[\"latency\"], dtype=np.float64)\n", " \n", - " total_time_all = time.perf_counter() - start_time_all\n", - " latency = np.array(latency)\n", + " min_len = min(len(fen_lat), len(zobrist_lat))\n", + " fen_lat = fen_lat[:min_len]\n", + " zobrist_lat = zobrist_lat[:min_len]\n", + " \n", + " # PAIRED T-Test (Comparaison median)\n", + " t_stat, t_p = stats.ttest_rel(fen_lat, zobrist_lat)\n", + " \n", + " # KS Test (Comparaison distribution)\n", + " ks_stat, ks_p = stats.ks_2samp(fen_lat, zobrist_lat)\n", + " \n", + " df_stats = pd.DataFrame({\n", + " \"test\": [\"paired t-test\", \"KS-test\"],\n", + " \"statistic\": [t_stat, ks_stat],\n", + " \"p-value\": [t_p, ks_p],\n", + " \"interpretation\": [\n", + " \"H0: mean latency equal (FEN vs Zobrist)\",\n", + " \"H0: same latency distribution (FEN vs Zobrist)\"\n", + " ]\n", + " })\n", + " \n", + " print(df_stats)\n", + " return df_stats\n", + "\n", + "def run_experiment():\n", + " redis_server = redis.Redis(host=\"localhost\", port=6379, db=0)\n", + " redis_zobrist = redis.Redis(host=\"localhost\", port=6379, db=3)\n", + " tablebases = chess.syzygy.open_tablebase(\"tablebases/\")\n", + " configs = [\"KBNvK\",\"KPvK\",\"KPvKP\",\"KQvK\",\"KQvKP\",\"KRBvKP\",\"KRPvKP\",\"KRvK\",\"KRvKB\"]\n", + " all_positions = []\n", + " for config in configs:\n", + " all_positions += get_250_random_position(tablebases, config)\n", + " valid_positions = []\n", + " for board in all_positions:\n", + " try:\n", + " tablebases.probe_wdl(board)\n", + " valid_positions.append(board)\n", + " except chess.syzygy.MissingTableError:\n", + " continue\n", + " print(\"BENCHMARK REDIS (FEN)\")\n", + " bench_redis = benchmark_redis(redis_server, valid_positions)\n", + " print(bench_redis)\n", + " print(\"BENCHMARK REDIS (Zobrist)\")\n", + " bench_zobrist = benchmark_redis_zobrist(redis_zobrist, valid_positions)\n", + " print(bench_zobrist)\n", + " print(f\"Redis FEN latency array length: {len(bench_redis['latency'])}\")\n", + " print(f\"Redis Hash latency array length: {len(bench_zobrist['latency'])}\")\n", + " print(\"\\n\" + \"=\" * 60)\n", + " print(\"MEMORY USAGE BY KEY (ESTIMATION)\")\n", + " print(\"=\" * 60)\n", + "\n", + " # Échantillonner 50 clés de chaque base\n", + " sample_fen = [redis_server.randomkey() for _ in range(50)]\n", + " mem_per_key_fen = [redis_server.memory_usage(k) for k in sample_fen if k]\n", + " avg_fen = np.mean(mem_per_key_fen)\n", + "\n", + " sample_zobrist = [redis_zobrist.randomkey() for _ in range(50)]\n", + " mem_per_key_zobrist = [redis_zobrist.memory_usage(k) for k in sample_zobrist if k]\n", + " avg_zobrist = np.mean(mem_per_key_zobrist)\n", + "\n", + " nb_keys = redis_server.dbsize()\n", + " estimated_fen = avg_fen * nb_keys\n", + " estimated_zobrist = avg_zobrist * nb_keys\n", + "\n", + " print(f\"Taille moyenne clé FEN: {avg_fen:.2f} bytes\")\n", + " print(f\"Taille moyenne clé Zobrist: {avg_zobrist:.2f} bytes\")\n", + " print(f\"\\nEstimation pour {nb_keys} clés:\")\n", + " print(f\" FEN: {estimated_fen / (1024*1024):.2f} MB\")\n", + " print(f\" Zobrist: {estimated_zobrist / (1024*1024):.2f} MB\")\n", + " print(f\" Gain: {(1 - avg_zobrist/avg_fen) * 100:.1f}%\")\n", + " print(\"=\" * 60)\n", " \n", " return {\n", - " \"count\": len(pos),\n", - " \"average_latency\": np.mean(latency),\n", - " \"p50\": np.percentile(latency, 50),\n", - " \"p90\": np.percentile(latency, 90),\n", - " \"p95\": np.percentile(latency, 95),\n", - " \"p99\": np.percentile(latency, 99),\n", - " \"std\": np.std(latency),\n", - " \"min\": np.min(latency),\n", - " \"max\": np.max(latency),\n", - " \"total_time\": total_time_all,\n", - " \"latency\": latency,\n", - " \"rps\": len(latency) / total_time_all,\n", - " }" + " \"bench_redis\": bench_redis,\n", + " \"bench_zobrist\": bench_zobrist\n", + " }\n", + "\n", + "if __name__ == \"__main__\":\n", + " results = run_experiment()\n", + " statistical_tests(results[\"bench_redis\"], results[\"bench_zobrist\"])\n", + " " ] }, { @@ -687,7 +750,7 @@ "id": "feb0d3b9", "metadata": {}, "source": [ - "# Partie Test 4\n", + "# Test de l'hypothèse 4\n", "\n", "Les cellules suivantes nous permettents de faire le test concernant les fonctions de Hash.\n", "On doit donc créer le benchmark ainsi que peupler Redis avec les tables hashées." @@ -742,7 +805,7 @@ " positions = []\n", " tries = 0\n", "\n", - " while len(positions) < 250 and tries < 500:\n", + " while len(positions) < 250 and tries < 5000:\n", " board = generate_board_from_config(config)\n", " try:\n", " tablebases.probe_wdl(board)\n", diff --git a/image/max_temps.png b/image/max_temps.png new file mode 100644 index 0000000..1d4071c Binary files /dev/null and b/image/max_temps.png differ diff --git a/image/min_temps.png b/image/min_temps.png new file mode 100644 index 0000000..2e4c8f8 Binary files /dev/null and b/image/min_temps.png differ diff --git a/image/moyenne_temps.png b/image/moyenne_temps.png new file mode 100644 index 0000000..81be112 Binary files /dev/null and b/image/moyenne_temps.png differ diff --git a/rapport.md b/rapport.md index 4235f46..0f44753 100644 --- a/rapport.md +++ b/rapport.md @@ -69,6 +69,248 @@ Dans le cas de Syzygy, la chaîne FEN est transformée en un index interne ou ha Dans Redis, cette même FEN peut servir telle quelle comme clé (string) d’où l’on peut associer des valeurs comme WDL ou DTZ. Cette approche garantit qu'une position donnée correspond toujours à une clé unique et reproductible, ce qui permet un accès direct à l’information associée, sans ambiguïté. +# Approche Scientifique et Méthodologie Expérimentale + +Cette section décrit la méthodologie utilisée pour évaluer les performances de **Redis** par rapport à **Syzygy** dans le stockage et l’accès aux positions d’échecs. +L’objectif est d’obtenir des mesures **reproductibles**, **interprétables** et **valides**, tout en identifiant les biais possibles pouvant affecter l’analyse. + +## Objectifs expérimentaux + +Les expériences menées ont pour but de répondre aux hypothèses suivantes : + +- **H1 – Incidence du nombre de pièces** + Nous supposons que le nombre de pièces présentes sur l’échiquier n’influe pas sur les performances d’accès de Redis. + +- **H2 – Gain de performance** + Nous supposons que Redis, en tant que base de données spécialisée dans les systèmes *key/value*, offre de meilleures performances que l’accès direct sur disque aux tablebases **Syzygy**. + De plus, Redis stockant ses paires clé/valeur en mémoire vive, nous nous attendons à un gain de performance significatif dû au temps d’accès à la RAM, bien inférieur à celui du disque. + +- **H3 – Optimisation des clés via Zobrist** + Nous supposons que l’utilisation du hash **Zobrist** fourni par la bibliothèque *python-chess* permet d’améliorer le temps d’accès ou l’utilisation mémoire par rapport à une clé FEN classique. + +- **H4 – Optimisation des clés via un hash classique** + Nous supposons que l’utilisation d’un hash classique permet également d’améliorer le temps d’accès ou l’utilisation mémoire, mais de manière moins efficace que le hash Zobrist. + +## Environnement expérimental + +Cette section décrit l’environnement matériel et logiciel utilisé pour la réalisation des expériences (configuration matérielle, système d’exploitation, versions des logiciels et bibliothèques). + +## Méthodologie de mesure + +Afin de vérifier chacune de ces hypothèses, nous étudions différentes métriques sur des jeux de données de test : + +1. **Temps moyen d’exécution** : + mesure du temps nécessaire pour accéder à une position dans Redis ou Syzygy, analysée à l’aide de tests statistiques. + +2. **Espace mémoire utilisé par Redis** : + évaluation de la consommation de mémoire vive à partir des outils fournis par Redis. + +3. **Nombre de requêtes par seconde (RPS)** : + mesure du débit maximal supporté par chaque solution. + +4. **Temps médian et percentile 95** : + analyse de la dispersion des temps d’accès et des valeurs extrêmes. + +## Génération des positions + +Pour chacun de ces tests, nous utilisons une base de **2250 positions**, tirées des configurations de tablebases **Syzygy** que nous avons exploitées. +Ce nombre a été choisi afin de permettre l’exécution des tests sur des machines disposant de capacités mémoire limitées, quelle que soit la quantité de mémoire vive (RAM) disponible. + +## Comparatif + +Étant donné que le même jeu de données est utilisé pour l’ensemble des expériences et que plusieurs machines sont à disposition, nous pouvons comparer le comportement de Redis en fonction : + +- de la quantité de mémoire RAM, +- du type de disque utilisé, +- du processeur. + +Nous analysons ainsi l’impact de ces paramètres sur le temps de réponse et l’utilisation des ressources. +Si possible, nous envisageons également d’étendre les expériences à des jeux de données plus grands ou plus petits afin d’obtenir des statistiques plus fines et représentatives. + +# Résultats et Interprétations + +## Test 1 : Incidence du nombre de pièces + +Afin de tester si le nombre de pièces présentes dans une configuration a une incidence sur le temps de chargement depuis **Redis**, nous avons réalisé un script permettant d’effectuer plusieurs requêtes sur un serveur Redis pour des configurations à **3, 4 et 5 pièces**. + +Pour chaque configuration, nous avons exécuté un nombre variable de requêtes : + +- 100 +- 1 000 +- 5 000 +- 10 000 +- 50 000 +- 100 000 + +Pour chaque test, nous avons mesuré le **temps minimal**, **moyen** et **maximal** d’accès aux positions. + +Après analyse des valeurs obtenues, nous constatons que les temps mesurés restent très proches, malgré l’augmentation significative du nombre de requêtes. +De plus, que ce soit pour **3 ou 5 pièces**, on observe sur la **Figure 1** que les temps moyens restent de l’ordre du **dixième de seconde**. + +![Moyenne du temps de requête](image/moyenne_temps.png) + +Les graphiques des temps minimum et maximum montrent également des valeurs cohérentes. Les quelques valeurs aberrantes observées sont probablement liées au matériel plutôt qu’à l’algorithme lui-même. + +En effet, la documentation indique que la mémoire **DDR4** cadencée autour de **3 000 MHz** permet des débits de lecture d’environ **20 Go/s**. Étant donné que les tables utilisées ne dépassent pas **20 Mo**, les résultats obtenus apparaissent cohérents. + +Concernant les temps minimum et maximum, ceux-ci étant fortement dépendants du comportement matériel, ils sont moins représentatifs et ne permettent pas de tirer des conclusions solides. +Néanmoins, l’analyse des temps minimum montre des valeurs proches entre les différentes configurations, ce qui confirme l’absence d’impact significatif du nombre de pièces sur les performances. + +## Test 2 : Gain de performances + +Après avoir testé **Syzygy** et **Redis** sur un même jeu de données (voir *Notebook Python*), nous sommes arrivés aux conclusions suivantes : + +- Redis est **près de 11 fois plus rapide** que Syzygy. +- La latence moyenne de Redis est de **0,35 ms**, contre **3,82 ms** pour Syzygy. +- La médiane est également plus faible pour Redis (**0,33 ms**) que pour Syzygy (**1,06 ms**). +- Redis permet en moyenne **1 969 requêtes par seconde**, contre seulement **280 requêtes par seconde** pour Syzygy. + +Ces résultats sont **statistiquement significatifs**, comme le montrent : +- le test *t* de Student apparié (*t* = 92,45, *p* < 0,001), +- le test de Kolmogorov–Smirnov (*KS* = 0,708, *p* < 0,001). + +Ces résultats valident l’hypothèse **H2**. + +## Test 3 : Optimisation des clés avec le hash Zobrist + +Afin de vérifier si une autre structure de clé pouvait améliorer les performances ou l’utilisation mémoire, nous avons testé, en plus du stockage classique via la **FEN**, l’utilisation du **hash Zobrist** fourni par la bibliothèque *python-chess*. + +La comparaison entre la base utilisant des clés FEN et celle utilisant des clés Zobrist met en évidence les points suivants : + +- Le hash Zobrist est environ **20 % plus rapide** que le stockage en FEN + (moyenne de **252,5 s** contre **308,1 s**). +- Le débit est nettement supérieur, avec en moyenne **55 % de requêtes supplémentaires** + (**3 405** contre **2 197**). +- L’utilisation mémoire est également améliorée, avec une clé FEN occupant en moyenne + **117,26 bytes/clé**, contre **107,64 bytes/clé** pour Zobrist (gain d’environ **8 %**). + +Ces résultats sont **statistiquement significatifs**, selon : +- le test *t* de Student apparié (*t* = 111,79, *p* < 0,001), +- le test de Kolmogorov–Smirnov (*KS* = 0,45, *p* < 0,001). + +L’hypothèse **H3** est donc validée. + +## Test 4 : Optimisation des clés avec un hash classique + +Pour vérifier l’hypothèse **H4**, nous avons créé une base de données utilisant des **clés hashées classiques**, puis comparé ses performances avec une base utilisant des clés FEN. + +Les résultats observés sont les suivants : + +- Avec les clés FEN : + - latence moyenne de **0,709 ms**, + - **1 068 requêtes par seconde**. +- Avec les clés hashées : + - latence moyenne de **0,448 ms**, + - **1 496 requêtes par seconde** en moyenne. + +Cela représente une amélioration de plus de **35 %** en termes de latence et de débit. + +Le gain est également notable sur la consommation mémoire : +- une clé FEN occupe en moyenne **136,46 bytes**, +- une clé hashée occupe en moyenne **97,94 bytes**, + +soit un gain de **28,3 %**. + +Ces résultats sont **statistiquement significatifs**, comme l’indiquent : +- le test *t* de Student apparié (*t* = -44,042, *p* < 0,001), +- le test de Kolmogorov–Smirnov (*KS* = 0,69, *p* < 0,001). + +L’hypothèse **H4** est ainsi validée. + +# Conclusion + +## Synthèse des résultats + +Avec l’aide de nos expériences, nous avons pu montrer que d’autres solutions plus rapides que +les solutions actuellement mises en place par les sites d’échecs existent. En effet, nous avons vu +que Redis est nettement plus rapide que Syzygy et qu’il offre également un débit presque 7 fois +supérieur. Il en va de même pour la façon de stocker les positions.Si l’on garde le système de +stockage classique des échecs, on ralentit encore plus le système, car les fonctions de hashage +nous permettent également de gagner en performance et en mémoire, avec pour chacune des +fonctions, une augmentation de minimum 18% en termes de débit mais aussi d’un gain en +mémoire conséquent avec plus de 25% pour la fonction de hashage classique. La question se +pose alors de pourquoi l’on n’utilise pas Redis et le hashage. Utiliser le hashage reviendrait à +perdre de l’information, ce qui est dans ce cas contradictoire comme c’est justement ce que l’on +cherche à stocker. Enfin, le gain entre Redis et Syzygy est minime et n’est pas pertinent pour +plusieurs raisons : Redis en tant que système in-memory est inadapté pour les Tablebases qui +font des centaines de Go, il faut donc un système sur disque où Syzygy est bien meilleur grâce +à la compression. L’accès aux Tablebases est aussi très rare et se fait de manière ciblée, sur +une position et s’il y a un changement, cela se tourne vers une position très similaire, le gain de +performances de Redis est donc moins pertinent dans le véritable usage. Enfin, il ne serait pas +possible économiquement de maintenir un tel système avec Redis. + +Cette étude nous a néanmoins permis de comparer les différences de performances entre les +approches disques (Syzygy) et les approches mémoires (Redis), de démontrer le poids du choix +de la structure des clés et de mieux comprendre quel système doit être utilisé selon le contexte. + +Pour conclure, bien que Redis et le hashage offrent des gains non négligeables, le système +actuel utilisant la FEN et Syzygy reste le meilleur en s’appuyant sur l’efficacité du stockage et +la maintenabilité que la performance brute dans le cadre du stockage complet des Tablebases. + +## Limites de l’étude + +Même si les résultats obtenus sont informatifs, plusieurs aspects doivent être pris en compte. +Tout d’abord, notre étude ne s’appuie que sur un sous-ensemble réduit des véritables tablebases +Syzygy : seules les configurations jusqu’à 5 pièces ont été testées, ce qui exclut les tablebases +plus volumineuses à 6 et 7 pièces. L’échantillon utilisé reste donc limité et ne permet pas de +tirer des conclusions définitives sur la performance à grande échelle. + +Les expériences ont été menées sur nos machines personnelles plutôt que sur des serveurs +dédiés ou des configurations hautes performances utilisées par certaines organisations spécialisées.[8] +Cela peut influencer à la fois la consommation mémoire observée et les temps d’accès, et limite +la généralisation des résultats. + +Enfin, notre analyse s’est concentrée uniquement sur les aspects performance (latence, RPS, +mémoire vive). D’autres dimensions importantes des tablebases — telles que la fiabilité, la com- +pression, l’intégrité des données, ou encore les implications pratiques pour les moteurs d’échecs +— n’ont pas été explorées dans cette étude. + +À ces limitations s’ajoute une contrainte méthodologique liée à la mesure de la mémoire : +Redis stocke et traite ses données exclusivement en mémoire vive, ce qui rend impossible une +séparation nette entre mémoire de stockage et mémoire d’exécution. Pour Syzygy, nous avons dû +mesurer la mémoire du processus via la bibliothèque psutil, tandis que pour Redis nous n’avons +accès qu’à la consommation totale rapportée par le serveur. Nous avons observé que Syzygy +a utilisé environ 205Mo de RAM pour exécuter et lire des tablebases qui est 21,6 Mo, tandis +que Redis a consommé seulement environ 169 Mo pour le même jeu de données. Toutefois, ces +valeurs sont fortement dépendantes de la taille réduite du dataset utilisé. Sur des bases plus +importantes, la consommation de Redis pourrait croı̂tre beaucoup plus rapidement, rendant les +comparaisons moins triviales. + +## Ouverture (Tablebases à 8 pièces) + +Depuis 2012, Les Tablebases à 7 pièces existent, mais il a fallu attendre août 2018 avant qu’elles +soient complètes. Ceci illustre la difficulté du jeu d’échecs et montre pourquoi, au contraire +d’autres jeux comme le jeu de dames, ne sera pas résolu. Actuellement, les Tablebases à 8 pièces +ont 15 % des finales sans pions résolues. [10] Ceci montre la difficulté de la progression pour finir +les Tablebases. De plus, on estime la taille totale à plusieurs Pétaoctects, ce qui ne serait même +pas utilisable pour la majorité des ordinateurs, même les plus puissants. Ceci vient du fait que +la base serait 90 fois plus grande que la base à 7 pièces. Enfin ceci aurait seulement un intérêt +théorique et changerait juste la vision de certaines finales (certaines fins de partie autrefois +considérées comme perdantes ont été classifiées comme nulle avec l’apparition des tablebases). + + +# Annexe + +Figure 2 : Minimum du temps de requête en fonction du nombre de pièces et du nombre de requêtes. + +![Minimum du temps de requête](image/min_temps.png) + +Figure 3 : Maximum du temps de requête en fonction du nombre de pièces et du nombre de requêtes. + +![Maximum du temps de requête](image/max_temps.png) + + +# Glossaire + +**FEN** +Notation standard utilisée dans le monde des échecs pour exprimer une position en chaı̂ne +de caractères.. + +**Tablebase** +Base de données exhaustive, contenant toutes les possibilités dans un monde limité +ainsi donc que leur résultat optimal.. + + ## Références - **1997 : l'ordinateur bat Garry Kasparov, un tournant dans l'histoire des échecs**