Conversation
gsvgit
left a comment
There was a problem hiding this comment.
Лучше явно разделить дерево и множество.
|
|
||
| let value n = | ||
| match n with | ||
| | Empty -> failwith "Empty node has no value" |
There was a problem hiding this comment.
Посмотрите внимательнее. Мы стараемся не использовать failwith. Пользуемся result вместо этого.
There was a problem hiding this comment.
А имеет ли смысл Result, если скрыть от пользователя модули Tree и Node? Для пользователя есть только модуль AVLSet, который гарантирует, что эти ошибки в принципе недостижимы (по аксиомам АВЛ-дерева) при вызове функций, иначе ошибка в логике. Все-таки у меня задача написать множество на основе дерева, а не само дерево.
| balance ln rnNew v | ||
| | _ -> | ||
| match right with | ||
| | Empty -> failwith "Unreacheable message 2" |
There was a problem hiding this comment.
Не очень информативное сообщение.
There was a problem hiding this comment.
Изменил их все на более информативные, но также оставил предложение, что эти сообщения в принципе не должны быть достижимы.
| let mutable leftUnion = Empty | ||
| let mutable rightUnion = Empty | ||
|
|
||
| Parallel.Invoke( |
There was a problem hiding this comment.
Уверены, что через таски - лучший вариант?
Ну и в целом, посмотрите, как пространства имён в .net устроены. Например, на array.parallel
There was a problem hiding this comment.
Переделал все на async. Разделил пространства
|
Да. Внятное описание к реквесту тоже не будет лишним. |
|
gsvgit
left a comment
There was a problem hiding this comment.
У Вас конфликты. И не надо липить сопоставление с образцом везде.
|
|
||
| let value n = | ||
| match n with | ||
| | Empty -> failwith "Empty node has no value. This error should be unreachable" |
There was a problem hiding this comment.
Посмотрите на другой код в библиотеке. Мы не используем Failwith (стараемся, по крайней мере). Вместо этого --- Result
| match n with | ||
| | Empty -> Node(0, value, Empty, Empty) | ||
| | Node(h, v, ln, rn) -> | ||
| match value with |
|
|
||
| module ParallelAVLSet = | ||
| let rec unionAsync threads set1 set2 = | ||
| async { |
There was a problem hiding this comment.
А async точно быстрее? Почему его выбрали?
There was a problem hiding this comment.
Нет, он не быстрее тасков, если говорить о работе с деревьями. Но он подходит больше для ФП стиля (без mutable), чем таски. Плюс у Async холодный старт. Поэтому и изменил
There was a problem hiding this comment.
Видимо, вопрос в том, зачем именно Вы делали параллельную версию.
There was a problem hiding this comment.
Сравнить производмтельность операций с разными реализациями. Параллельные гораздо быстрее
There was a problem hiding this comment.
Было бы здорово добавить описание бенчмарок (примерно, как тут сделано). Ну и ссылку в основное ребми тоже. А в Вашем случае ещё и результаты замеров хотя бы в описании реквеста сделать.
There was a problem hiding this comment.
Я займусь этим сегодня, просто сейчас много контрольных и зачетов, готовился/готовлюсь. Извините
|
|
Насчет Result: Я не совсем понимаю необходимость в нем. Стандартные библиотеки в F# также выбрасывают исключение в случае передачи невалидных аргументов. А если я перепишу все на Result, то у меня буквально весь код и все операции превратятся в нагромождение Ok и Error, даже функция add будет это возвращать, хотя это не очень удбно для конвейера |
Необходимость в том, что это прототип и будет переписываться на языки, где исключений нет. А чтобы не было чуть проще жить, в основной ветке уже даже есть workflow соответствующий. Можете изучить, ребейзнуться и пользоваться. |
|
Удалил все исключения и заменил все на Result<AVLSet<'A>,AVLSetError>. Ну, и, соответственно, бенчмарки с тестами тоже |
|
Что-то всё попадало. И да. Спасибо за ворох сырых данных. Оформите аккуртано самое важное в ридми. |
|
А чего так скромно? Может стоит какие-то аккуратные графики или таблички привести. |
|
|
||
| ### Benchmark Results | ||
|
|
||
| Based on the benchmarking data obtained via BenchmarkDotNet, we can draw comprehensive architectural conclusions regarding asymptotic complexity, algorithmic trade-offs, and multi-threading overhead in immutable data structures. |
|
И конфликты. |
|
Можно узнать, где конфликты именно? У меня гит пишет, что все актуально |
gsvgit
left a comment
There was a problem hiding this comment.
А что с параллельностью в итоге?
Danil-Zaripov
left a comment
There was a problem hiding this comment.
The code is fine, I guess. But the benchmark description begs to be rewritten by hand.
| * **Time Complexity:** $O(\log N)$. Scaling tree size by 1,000x (100 $\rightarrow$ 100,000 nodes) increases execution time by only ~2.3x. | ||
| * **Memory Allocation:** Scales logarithmically due to standard path-copying overhead in immutable structures (880 B at 100 nodes $\rightarrow$ 2,080 B at 100,000 nodes). | ||
|
|
||
| #### 2. Traversal vs. Sequential Set Operations | ||
| Performance is strictly bound to the $|A| / |B|$ size ratio. | ||
| * **$|A| \gg |B|$:** `Traversal` is optimal. Yields ~3.8x speedup (e.g., Intersection, 100k $\times$ 100). | ||
| * **$|A| \ll |B|$:** `Traversal` is slow. Yields ~41x slowdown (e.g., Difference, 100 $\times$ 10k). | ||
|
|
||
| #### 3. Parallel Set Operations | ||
| * **Tasks:** Balanced recursive partitioning prevents the generation of excessive micro-tasks, significantly reducing thread pool scheduling overhead. | ||
| * **Thread Contention:** Improved cache locality and minimized context switching allow execution time to scale effectively with the thread count. | ||
| * **GC Thrashing:** Memory allocation rates are now strictly controlled (nearly matching the sequential baseline, e.g., ~81.6 MB vs ~80.7 MB for a $100k \times 100k$ operation), completely preventing Gen0 garbage collection thrashing. | ||
|
|
||
| #### Table | ||
|
|
||
| | Operation Scenario (A × B) | Implementation Type | Execution Time | Memory Allocated | Ratio | Algorithmic Insight | | ||
| | --- | --- | --- | --- | --- | --- | | ||
| | **Single Add** (100) | Sequential | 582.1 ns | 880 B | 1.00 (Base) | Logarithmic $O(\log N)$ algorithm. | | ||
| | **Single Add** (100,000) | Sequential | 1,376.7 ns | 2,080 B | ~2.3x scales | Expected path-copying cost. | | ||
| | --- | --- | --- | --- | --- | --- | | ||
| | **Intersection** (100k × 100) | Sequential | 318.25 μs | 447.82 KB | 1.00 (Base) | Standard recursive intersection. | | ||
| | **Intersection** (100k × 100) | Tree Traversal | **83.99 μs** | **86.54 KB** | **~3.8x Speedup** | Huge $A \gg B$ asymmetry. | | ||
| | --- | --- | --- | --- | --- | --- | | ||
| | **Difference** (100 × 10k) | Sequential | 168.15 μs | 230.23 KB | 1.00 (Base) | Standard recursive difference. | | ||
| | **Difference** (100 × 10k) | Tree Traversal | 6,958.68 μs | 8.86 MB | **41.39x Slowdown** | Tree traversal slowdown. | | ||
| | --- | --- | --- | --- | --- | --- | | ||
| | **Union** (100k figure× 100k) | Sequential | 96.89 ms | 80.72 MB | 1.00 (Base) | Standard recursive union. | | ||
| | **Union** (100k × 100k) | Parallel (2 Threads) | **69.63 ms** | **81.61 MB** | **~1.39x Speedup** | Optimized parallel algorithm. | No newline at end of file |
There was a problem hiding this comment.
🤔. How much of it was written by hand?
| ### Benchmark results | ||
|
|
||
| #### 1. Single Element Operations | ||
| * **Time Complexity:** $O(\log N)$. Scaling tree size by 1,000x (100 $\rightarrow$ 100,000 nodes) increases execution time by only ~2.3x. |
There was a problem hiding this comment.
is this the direct conclusion from your benchmark?
- Encapsulate implementation by marking Node and Tree as internal. - Keep AVLSet module public as the primary API. - Implement InternalsVisibleTo to allow testing of internal structures. - Move parallel operations to a dedicated sub-module for better SoC. - Add FsCheck property-based tests for core set operations. - Fix and stabilize benchmarks.
…n matching, open modules, clean up redundant pattern matchings and replace them with if-else
No description provided.