From: softworkz <softwo...@hotmail.com> Signed-off-by: softworkz <softwo...@hotmail.com> --- libavutil/tests/dict2.c | 185 +++++++++++++++++++++++++++++ tests/api/Makefile | 1 + tests/api/api-dict2-test.c | 122 +++++++++++++++++++ tests/fate/api.mak | 15 +++ tools/Makefile | 2 +- tools/dict2_benchmark.c | 237 +++++++++++++++++++++++++++++++++++++ 6 files changed, 561 insertions(+), 1 deletion(-) create mode 100644 libavutil/tests/dict2.c create mode 100644 tests/api/api-dict2-test.c create mode 100644 tools/dict2_benchmark.c
diff --git a/libavutil/tests/dict2.c b/libavutil/tests/dict2.c new file mode 100644 index 0000000000..31c9f568f6 --- /dev/null +++ b/libavutil/tests/dict2.c @@ -0,0 +1,185 @@ +/* + * AVDictionary2 test utility + * This file is part of FFmpeg. + */ + +#include "libavutil/dict2.h" +#include "libavutil/dict.h" +#include "libavutil/time.h" +#include "libavutil/avassert.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +static void basic_functionality_test(void) +{ + printf("\n=== Basic Functionality Test ===\n"); + + AVDictionary2 *dict = NULL; + AVDictionaryEntry2 *entry; + int ret; + + // Test setting keys + ret = av_dict2_set(&dict, "key1", "value1", 0); + printf("Adding key1: %s\n", ret >= 0 ? "OK" : "FAILED"); + av_assert0(ret >= 0); + + ret = av_dict2_set(&dict, "key2", "value2", 0); + printf("Adding key2: %s\n", ret >= 0 ? "OK" : "FAILED"); + av_assert0(ret >= 0); + + // Test lookup + entry = av_dict2_get(dict, "key1", NULL, 0); + printf("Lookup key1: %s (value: %s)\n", + entry ? "OK" : "FAILED", + entry ? entry->value : "NULL"); + av_assert0(entry && !strcmp(entry->value, "value1")); + + // Test count + int count = av_dict2_count(dict); + printf("Dictionary count: %d (expected 2)\n", count); + av_assert0(count == 2); + + // Test iteration + printf("Dictionary contents:\n"); + const AVDictionaryEntry2 *iter = NULL; + while ((iter = av_dict2_iterate(dict, iter))) { + printf(" %s: %s\n", iter->key, iter->value); + } + + // Free dictionary + av_dict2_free(&dict); + printf("Dictionary freed successfully\n"); +} + +static void overwrite_test(void) +{ + printf("\n=== Overwrite Test ===\n"); + + AVDictionary2 *dict = NULL; + AVDictionaryEntry2 *entry; + + // Test normal overwrite + av_dict2_set(&dict, "key", "value1", 0); + av_dict2_set(&dict, "key", "value2", 0); + + entry = av_dict2_get(dict, "key", NULL, 0); + printf("Overwrite test: %s (value: %s, expected: value2)\n", + entry && !strcmp(entry->value, "value2") ? "OK" : "FAILED", + entry ? entry->value : "NULL"); + av_assert0(entry && !strcmp(entry->value, "value2")); + + // Test DONT_OVERWRITE flag + av_dict2_set(&dict, "key", "value3", AV_DICT2_DONT_OVERWRITE); + + entry = av_dict2_get(dict, "key", NULL, 0); + printf("DONT_OVERWRITE flag test: %s (value: %s, expected: value2)\n", + entry && !strcmp(entry->value, "value2") ? "OK" : "FAILED", + entry ? entry->value : "NULL"); + av_assert0(entry && !strcmp(entry->value, "value2")); + + av_dict2_free(&dict); +} + +static void case_sensitivity_test(void) +{ + printf("\n=== Case Sensitivity Test ===\n"); + + // Test case-sensitive dictionary with AV_DICT2_MATCH_CASE flag + AVDictionary2 *dict1 = NULL; + av_dict2_set(&dict1, "Key", "value1", AV_DICT2_MATCH_CASE); + + AVDictionaryEntry2 *entry1 = av_dict2_get(dict1, "key", NULL, AV_DICT2_MATCH_CASE); + printf("Case-sensitive lookup: %s (expected NULL)\n", + entry1 ? "FAILED" : "OK"); + av_assert0(entry1 == NULL); + + // Test case-insensitive dictionary (default behavior) + AVDictionary2 *dict2 = NULL; + av_dict2_set(&dict2, "Key", "value1", 0); + + AVDictionaryEntry2 *entry2 = av_dict2_get(dict2, "key", NULL, 0); + printf("Case-insensitive lookup: %s (value: %s)\n", + entry2 ? "OK" : "FAILED", + entry2 ? entry2->value : "NULL"); + av_assert0(entry2 && !strcmp(entry2->value, "value1")); + + av_dict2_free(&dict1); + av_dict2_free(&dict2); +} + +static void stress_test(void) +{ + printf("\n=== Stress Test ===\n"); + + AVDictionary2 *dict = NULL; + char key[32], value[32]; + int i, count, lookup_successful = 0; + int64_t start_time, elapsed; + + // Create a large number of entries + const int num_entries = 10000; + printf("Creating %d entries...\n", num_entries); + + start_time = av_gettime(); + for (i = 0; i < num_entries; i++) { + sprintf(key, "key%d", i); + sprintf(value, "value%d", i); + av_dict2_set(&dict, key, value, 0); + } + elapsed = av_gettime() - start_time; + printf("Insertion time: %" PRId64 " us (%.2f us per entry)\n", + elapsed, (double)elapsed / num_entries); + + // Test lookup of all keys + printf("Looking up all keys...\n"); + start_time = av_gettime(); + for (i = 0; i < num_entries; i++) { + sprintf(key, "key%d", i); + AVDictionaryEntry2 *entry = av_dict2_get(dict, key, NULL, 0); + if (entry) lookup_successful++; + } + elapsed = av_gettime() - start_time; + printf("Lookup time: %" PRId64 " us (%.2f us per lookup)\n", + elapsed, (double)elapsed / num_entries); + printf("Found %d of %d entries\n", lookup_successful, num_entries); + av_assert0(lookup_successful == num_entries); + + // Check count + count = av_dict2_count(dict); + printf("Dictionary count: %d (expected %d)\n", count, num_entries); + av_assert0(count == num_entries); + + // Free dictionary and measure cleanup time + start_time = av_gettime(); + av_dict2_free(&dict); + elapsed = av_gettime() - start_time; + printf("Cleanup time: %" PRId64 " us\n", elapsed); + printf("Stress test completed successfully\n"); +} + +int main(int argc, char **argv) +{ + printf("AVDictionary2 Test Suite\n"); + printf("========================\n"); + + // Check if specific test is requested + int run_stress = 0; + if (argc >= 2 && !strcmp(argv[1], "stress")) { + run_stress = 1; + } + + // Always run basic tests + basic_functionality_test(); + overwrite_test(); + case_sensitivity_test(); + + // Run stress test if requested + if (run_stress) { + stress_test(); + } + + printf("\nAll tests PASSED!\n"); + return 0; +} diff --git a/tests/api/Makefile b/tests/api/Makefile index c96e636756..4d069f7bae 100644 --- a/tests/api/Makefile +++ b/tests/api/Makefile @@ -2,6 +2,7 @@ APITESTPROGS-$(call ENCDEC, FLAC, FLAC) += api-flac APITESTPROGS-$(call DEMDEC, H264, H264) += api-h264 APITESTPROGS-$(call DEMDEC, H264, H264) += api-h264-slice APITESTPROGS-yes += api-seek +APITESTPROGS-yes += api-dict2 APITESTPROGS-$(call DEMDEC, H263, H263) += api-band APITESTPROGS-$(HAVE_THREADS) += api-threadmessage APITESTPROGS += $(APITESTPROGS-yes) diff --git a/tests/api/api-dict2-test.c b/tests/api/api-dict2-test.c new file mode 100644 index 0000000000..a120a3488c --- /dev/null +++ b/tests/api/api-dict2-test.c @@ -0,0 +1,122 @@ +/* + * AVDictionary2 test utility + * This file is part of FFmpeg. + */ + +#include "libavutil/dict2.h" +#include "libavutil/dict.h" +#include "libavutil/time.h" +#include "libavutil/avassert.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static void basic_functionality_test(void) +{ + printf("\n=== Basic Functionality Test ===\n"); + + AVDictionary2 *dict = NULL; + AVDictionaryEntry2 *entry; + int ret; + + // Test setting keys + ret = av_dict2_set(&dict, "key1", "value1", 0); + printf("Adding key1: %s\n", ret >= 0 ? "OK" : "FAILED"); + av_assert0(ret >= 0); + + ret = av_dict2_set(&dict, "key2", "value2", 0); + printf("Adding key2: %s\n", ret >= 0 ? "OK" : "FAILED"); + av_assert0(ret >= 0); + + // Test lookup + entry = av_dict2_get(dict, "key1", NULL, 0); + printf("Lookup key1: %s (value: %s)\n", + entry ? "OK" : "FAILED", + entry ? entry->value : "NULL"); + av_assert0(entry && !strcmp(entry->value, "value1")); + + // Test count + int count = av_dict2_count(dict); + printf("Dictionary count: %d (expected 2)\n", count); + av_assert0(count == 2); + + // Test iteration + printf("Dictionary contents:\n"); + const AVDictionaryEntry2 *iter = NULL; + while ((iter = av_dict2_iterate(dict, iter))) { + printf(" %s: %s\n", iter->key, iter->value); + } + + // Free dictionary + av_dict2_free(&dict); + printf("Dictionary freed successfully\n"); +} + +static void overwrite_test(void) +{ + printf("\n=== Overwrite Test ===\n"); + + AVDictionary2 *dict = NULL; + AVDictionaryEntry2 *entry; + + // Test normal overwrite + av_dict2_set(&dict, "key", "value1", 0); + av_dict2_set(&dict, "key", "value2", 0); + + entry = av_dict2_get(dict, "key", NULL, 0); + printf("Overwrite test: %s (value: %s expected: value2)\n", + entry && !strcmp(entry->value, "value2") ? "OK" : "FAILED", + entry ? entry->value : "NULL"); + av_assert0(entry && !strcmp(entry->value, "value2")); + + // Test DONT_OVERWRITE flag + av_dict2_set(&dict, "key", "value3", AV_DICT2_DONT_OVERWRITE); + + entry = av_dict2_get(dict, "key", NULL, 0); + printf("DONT_OVERWRITE flag test: %s (value: %s expected: value2)\n", + entry && !strcmp(entry->value, "value2") ? "OK" : "FAILED", + entry ? entry->value : "NULL"); + av_assert0(entry && !strcmp(entry->value, "value2")); + + av_dict2_free(&dict); +} + +static void case_sensitivity_test(void) +{ + printf("\n=== Case Sensitivity Test ===\n"); + + // Test case-sensitive dictionary with AV_DICT2_MATCH_CASE flag + AVDictionary2 *dict1 = NULL; + av_dict2_set(&dict1, "Key", "value1", AV_DICT2_MATCH_CASE); + + AVDictionaryEntry2 *entry1 = av_dict2_get(dict1, "key", NULL, AV_DICT2_MATCH_CASE); + printf("Case-sensitive lookup: %s (expected NULL)\n", + entry1 ? "FAILED" : "OK"); + av_assert0(entry1 == NULL); + + // Test case-insensitive dictionary (default behavior) + AVDictionary2 *dict2 = NULL; + av_dict2_set(&dict2, "Key", "value1", 0); + + AVDictionaryEntry2 *entry2 = av_dict2_get(dict2, "key", NULL, 0); + printf("Case-insensitive lookup: %s (value: %s)\n", + entry2 ? "OK" : "FAILED", + entry2 ? entry2->value : "NULL"); + av_assert0(entry2 && !strcmp(entry2->value, "value1")); + + av_dict2_free(&dict1); + av_dict2_free(&dict2); +} + +int main(void) +{ + printf("AVDictionary2 Test Suite\n"); + printf("========================\n"); + + basic_functionality_test(); + overwrite_test(); + case_sensitivity_test(); + + printf("\nAll tests PASSED!\n"); + return 0; +} diff --git a/tests/fate/api.mak b/tests/fate/api.mak index d2868e57ac..18219eab1d 100644 --- a/tests/fate/api.mak +++ b/tests/fate/api.mak @@ -27,6 +27,21 @@ fate-api-threadmessage: $(APITESTSDIR)/api-threadmessage-test$(EXESUF) fate-api-threadmessage: CMD = run $(APITESTSDIR)/api-threadmessage-test$(EXESUF) 3 10 30 50 2 20 40 fate-api-threadmessage: CMP = null +FATE_API-yes += fate-api-dict2 +fate-api-dict2: $(APITESTSDIR)/api-dict2-test$(EXESUF) +fate-api-dict2: CMD = run $(APITESTSDIR)/api-dict2-test$(EXESUF) +fate-api-dict2: CMP = null + +# Dict2 implementation tests +FATE_DICT2 = fate-dict2-basic fate-dict2-stress +FATE_AVUTIL += $(FATE_DICT2) + +fate-dict2-basic: libavutil/tests/dict2$(EXESUF) +fate-dict2-basic: CMD = run libavutil/tests/dict2 basic + +fate-dict2-stress: libavutil/tests/dict2$(EXESUF) +fate-dict2-stress: CMD = run libavutil/tests/dict2 stress + FATE_API_SAMPLES-$(CONFIG_AVFORMAT) += $(FATE_API_SAMPLES_LIBAVFORMAT-yes) ifdef SAMPLES diff --git a/tools/Makefile b/tools/Makefile index 7ae6e3cb75..d0a5c45c80 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -1,4 +1,4 @@ -TOOLS = enc_recon_frame_test enum_options qt-faststart scale_slice_test trasher uncoded_frame +TOOLS = dict2_benchmark enc_recon_frame_test enum_options qt-faststart scale_slice_test trasher uncoded_frame TOOLS-$(CONFIG_LIBMYSOFA) += sofa2wavs TOOLS-$(CONFIG_ZLIB) += cws2fws diff --git a/tools/dict2_benchmark.c b/tools/dict2_benchmark.c new file mode 100644 index 0000000000..bdf34440b9 --- /dev/null +++ b/tools/dict2_benchmark.c @@ -0,0 +1,237 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * AVDictionary vs AVDictionary2 Benchmark + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include "libavutil/dict.h" +#include "libavutil/dict2.h" +#include "libavutil/time.h" + +#define RAND_STR_LEN 16 +#define TEST_ITERATIONS 5000 + +/* Generate random string */ +static void gen_random_str(char *s, int len) { + static const char alphanum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + int i; + + for (i = 0; i < len - 1; i++) { + s[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; + } + s[len - 1] = '\0'; +} + +/* Fill a dictionary with random key-value pairs */ +static void fill_dict(AVDictionary **dict, int count) { + int i; + char key[RAND_STR_LEN]; + char val[RAND_STR_LEN]; + + for (i = 0; i < count; i++) { + gen_random_str(key, RAND_STR_LEN); + gen_random_str(val, RAND_STR_LEN); + av_dict_set(dict, key, val, 0); + } +} + +/* Fill a dictionary2 with random key-value pairs */ +static void fill_dict2(AVDictionary2 **dict, int count) { + int i; + char key[RAND_STR_LEN]; + char val[RAND_STR_LEN]; + + for (i = 0; i < count; i++) { + gen_random_str(key, RAND_STR_LEN); + gen_random_str(val, RAND_STR_LEN); + av_dict2_set(dict, key, val, 0); + } +} + +/* Generate lookup keys: some existing and some new */ +static char **gen_lookup_keys(int count, AVDictionary *dict, int hit_ratio_percent) { + int i, hits = count * hit_ratio_percent / 100; + char **keys = malloc(count * sizeof(char *)); + if (!keys) return NULL; + + // First add some keys that exist in the dictionary + AVDictionaryEntry *entry = NULL; + for (i = 0; i < hits && i < count; i++) { + entry = av_dict_get(dict, "", entry, AV_DICT_IGNORE_SUFFIX); + if (!entry) break; // Not enough entries + + keys[i] = malloc(RAND_STR_LEN); + if (!keys[i]) { + while (--i >= 0) free(keys[i]); + free(keys); + return NULL; + } + strcpy(keys[i], entry->key); + } + + // Fill the rest with random keys (likely misses) + for (; i < count; i++) { + keys[i] = malloc(RAND_STR_LEN); + if (!keys[i]) { + while (--i >= 0) free(keys[i]); + free(keys); + return NULL; + } + gen_random_str(keys[i], RAND_STR_LEN); + } + + return keys; +} + +/* Free lookup keys */ +static void free_lookup_keys(char **keys, int count) { + int i; + for (i = 0; i < count; i++) { + free(keys[i]); + } + free(keys); +} + +int main(int argc, char *argv[]) +{ + int count = 1000; // Default dictionary size + double time_start, time_end, time_dict, time_dict2; + + // Parse command line for count + if (argc > 1) { + count = atoi(argv[1]); + if (count <= 0) count = 1000; + } + + printf("Benchmarking AVDictionary vs AVDictionary2 with %d entries\n\n", count); + + srand(1234); // Fixed seed for reproducibility + + // Setup dictionaries for insertion test + AVDictionary *dict1 = NULL; + AVDictionary2 *dict2 = NULL; + + // Benchmark 1: Insertion + printf("1. Insertion Performance:\n"); + + time_start = av_gettime_relative() / 1000.0; + fill_dict(&dict1, count); + time_end = av_gettime_relative() / 1000.0; + time_dict = time_end - time_start; + printf(" AVDictionary: %.3f ms\n", time_dict); + + time_start = av_gettime_relative() / 1000.0; + fill_dict2(&dict2, count); + time_end = av_gettime_relative() / 1000.0; + time_dict2 = time_end - time_start; + printf(" AVDictionary2: %.3f ms (%.1f%% of original time)\n", + time_dict2, time_dict2*100.0/time_dict); + + // Benchmark 2: Lookup (existing keys - 100% hit rate) + printf("\n2. Lookup Performance (100%% existing keys):\n"); + + char **lookup_keys = gen_lookup_keys(TEST_ITERATIONS, dict1, 100); + if (!lookup_keys) { + fprintf(stderr, "Failed to generate lookup keys\n"); + return 1; + } + + time_start = av_gettime_relative() / 1000.0; + for (int i = 0; i < TEST_ITERATIONS; i++) { + av_dict_get(dict1, lookup_keys[i], NULL, 0); + } + time_end = av_gettime_relative() / 1000.0; + time_dict = time_end - time_start; + printf(" AVDictionary: %.3f ms\n", time_dict); + + time_start = av_gettime_relative() / 1000.0; + for (int i = 0; i < TEST_ITERATIONS; i++) { + av_dict2_get(dict2, lookup_keys[i], NULL, 0); + } + time_end = av_gettime_relative() / 1000.0; + time_dict2 = time_end - time_start; + printf(" AVDictionary2: %.3f ms (%.1f%% of original time)\n", + time_dict2, time_dict2*100.0/time_dict); + + free_lookup_keys(lookup_keys, TEST_ITERATIONS); + + // Benchmark 3: Lookup (mixed keys - 50% hit rate) + printf("\n3. Lookup Performance (50%% existing keys):\n"); + + lookup_keys = gen_lookup_keys(TEST_ITERATIONS, dict1, 50); + if (!lookup_keys) { + fprintf(stderr, "Failed to generate lookup keys\n"); + return 1; + } + + time_start = av_gettime_relative() / 1000.0; + for (int i = 0; i < TEST_ITERATIONS; i++) { + av_dict_get(dict1, lookup_keys[i], NULL, 0); + } + time_end = av_gettime_relative() / 1000.0; + time_dict = time_end - time_start; + printf(" AVDictionary: %.3f ms\n", time_dict); + + time_start = av_gettime_relative() / 1000.0; + for (int i = 0; i < TEST_ITERATIONS; i++) { + av_dict2_get(dict2, lookup_keys[i], NULL, 0); + } + time_end = av_gettime_relative() / 1000.0; + time_dict2 = time_end - time_start; + printf(" AVDictionary2: %.3f ms (%.1f%% of original time)\n", + time_dict2, time_dict2*100.0/time_dict); + + free_lookup_keys(lookup_keys, TEST_ITERATIONS); + + // Benchmark 4: Iteration + printf("\n4. Iteration Performance:\n"); + + time_start = av_gettime_relative() / 1000.0; + AVDictionaryEntry *entry = NULL; + while ((entry = av_dict_get(dict1, "", entry, AV_DICT_IGNORE_SUFFIX))) { + // Just iterate + } + time_end = av_gettime_relative() / 1000.0; + time_dict = time_end - time_start; + printf(" AVDictionary: %.3f ms\n", time_dict); + + time_start = av_gettime_relative() / 1000.0; + const AVDictionaryEntry2 *entry2 = NULL; + while ((entry2 = av_dict2_iterate(dict2, entry2))) { + // Just iterate + } + time_end = av_gettime_relative() / 1000.0; + time_dict2 = time_end - time_start; + printf(" AVDictionary2: %.3f ms (%.1f%% of original time)\n", + time_dict2, time_dict2*100.0/time_dict); + + // Cleanup + av_dict_free(&dict1); + av_dict2_free(&dict2); + + printf("\nBenchmark completed successfully\n"); + return 0; +} -- ffmpeg-codebot _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".