Packages

High-performance Markdown processing library with SIMD optimizations, JSON-LD integration, and parallel batch processing.

Current section

Files

Jump to
markdown_ld lib markdown_ld.ex
Raw

lib/markdown_ld.ex

defmodule MarkdownLd do
@moduledoc """
High-performance Markdown processing with SIMD optimizations and JSON-LD integration.
MarkdownLd provides blazing-fast markdown processing with advanced SIMD optimizations,
memory pooling, and batch processing capabilities. Built on top of Rust with zero-copy
processing for maximum performance.
## Features
- **SIMD-optimized** string processing (Apple Silicon NEON, x86 AVX2)
- **Zero-copy** binary processing for maximum efficiency
- **Memory pool** management for reduced allocations
- **Parallel batch** processing with configurable concurrency
- **Comprehensive parsing** (links, headings, code blocks, tasks)
- **Pattern caching** for common structures
- **Performance tracking** and metrics
- **JSON-LD metadata** extraction (planned)
## Quick Start
# Add to your mix.exs
{:markdown_ld, "~> 0.4.0"}
# Basic parsing
{:ok, result} = MarkdownLd.parse("# Hello World")
# Batch processing
{:ok, results} = MarkdownLd.parse_batch(documents, max_workers: 4)
## Basic Examples
iex> {:ok, result} = MarkdownLd.parse(\"\"\"
...> # Hello World
...>
...> This is **bold** text with a [link](https://example.com).
...>
...> ```elixir
...> def hello, do: :world
...> ```
...>
...> - [ ] Todo item
...> - [x] Done item
...> \"\"\")
iex> result.headings
[%{level: 1, text: "Hello World", line: 1}]
iex> result.links
[%{text: "link", url: "https://example.com", line: 3}]
iex> result.code_blocks
[%{language: "elixir", content: "def hello, do: :world", line: 5}]
iex> result.tasks
[%{completed: false, text: "Todo item", line: 9},
%{completed: true, text: "Done item", line: 10}]
## Performance Examples
# Zero-copy binary processing (fastest)
binary_content = File.read!("large_document.md")
{:ok, result} = MarkdownLd.parse_binary(binary_content)
# Batch processing for multiple documents
documents = ["# Doc 1", "# Doc 2", "# Doc 3"]
{:ok, results} = MarkdownLd.parse_batch_rust(documents) # Rust parallel
{:ok, results} = MarkdownLd.parse_batch(documents) # Elixir parallel
# Stream processing with backpressure
large_document_stream
|> MarkdownLd.parse_stream(max_workers: 8)
|> Stream.filter(fn {:ok, result} -> length(result.headings) > 0 end)
|> Enum.take(100)
## Performance Tracking
# Get detailed performance metrics
{:ok, stats} = MarkdownLd.get_performance_stats()
# %{
# "simd_operations" => 1_250_000,
# "cache_hit_rate" => 85.2,
# "memory_pool_usage" => 2_048_576,
# "pattern_cache_size" => 128
# }
# Reset counters for benchmarking
MarkdownLd.reset_performance_stats()
## Configuration
# In your config.exs
config :markdown_ld,
parallel: true,
max_workers: System.schedulers_online(),
cache_patterns: true,
track_performance: true,
simd_enabled: true
# Per-operation configuration
{:ok, result} = MarkdownLd.parse(content,
parallel: false,
cache_patterns: true,
max_workers: 2
)
## Architecture
MarkdownLd uses a three-layer architecture for maximum performance:
1. **Elixir API Layer** - Batch processing, streaming, error handling
2. **Rust NIF Core** - Memory management, pattern caching, coordination
3. **SIMD Engine** - Vectorized operations, high-performance parsing
This design provides the safety and concurrency of Elixir with the raw
performance of optimized Rust code.
## Performance Characteristics
- **Small documents** (1KB): 3-7μs processing time
- **Medium documents** (10KB): 5-10μs processing time
- **Large documents** (100KB): 15-35μs processing time
- **Throughput**: Up to 5GB/s for large documents
- **Memory efficiency**: 50-80% reduction in allocations
- **Scalability**: Thousands of documents per second
See `PERFORMANCE_REPORT.md` for detailed benchmarking results.
"""
alias MarkdownLd.Native
@type parse_result :: %{
headings: [heading()],
links: [link()],
code_blocks: [code_block()],
tasks: [task()],
word_count: non_neg_integer(),
processing_time_us: non_neg_integer()
}
@type heading :: %{
level: 1..6,
text: String.t(),
line: pos_integer()
}
@type link :: %{
text: String.t(),
url: String.t(),
line: pos_integer()
}
@type code_block :: %{
language: String.t() | nil,
content: String.t(),
line: pos_integer()
}
@type task :: %{
completed: boolean(),
text: String.t(),
line: pos_integer()
}
@type parse_options :: [
parallel: boolean(),
max_workers: pos_integer(),
cache_patterns: boolean(),
track_performance: boolean()
]
@doc """
Parse markdown text with SIMD optimizations.
## Options
* `:parallel` - Enable parallel processing for large documents (default: false)
* `:max_workers` - Maximum number of worker processes (default: System.schedulers_online())
* `:cache_patterns` - Enable pattern caching for repeated structures (default: true)
* `:track_performance` - Include performance metrics in result (default: true)
## Examples
iex> MarkdownLd.parse("# Hello\\n[Link](http://example.com)")
{:ok, %{
headings: [%{level: 1, text: "Hello", line: 1}],
links: [%{text: "Link", url: "http://example.com", line: 2}],
word_count: 2,
processing_time_us: 156
}}
"""
@spec parse(String.t() | binary(), parse_options()) :: {:ok, parse_result()} | {:error, String.t()}
def parse(content, opts \\ [])
def parse(content, opts) when is_binary(content) do
opts = Keyword.merge(default_options(), opts)
Native.parse_markdown(content, opts)
end
@doc """
Parse markdown text with zero-copy optimization for binary input.
This function provides the fastest parsing path by avoiding string conversion
and operating directly on binary data with SIMD optimizations.
"""
@spec parse_binary(binary(), parse_options()) :: {:ok, parse_result()} | {:error, String.t()}
def parse_binary(binary, opts \\ []) when is_binary(binary) do
opts = Keyword.merge(default_options(), opts)
Native.parse_markdown_binary(binary, opts)
end
@doc """
Parse multiple markdown documents in parallel with batch optimization.
Uses Elixir-side parallelization with configurable worker processes.
Best for processing many small to medium documents.
## Examples
iex> docs = ["# Doc 1", "# Doc 2", "# Doc 3"]
iex> MarkdownLd.parse_batch(docs, max_workers: 4)
{:ok, [%{headings: [...]}, %{headings: [...]}, %{headings: [...]}]}
"""
@spec parse_batch([String.t() | binary()], parse_options()) :: {:ok, [parse_result()]} | {:error, String.t()}
def parse_batch(documents, opts \\ []) when is_list(documents) do
opts = Keyword.merge(default_options(), opts)
max_workers = Keyword.get(opts, :max_workers, System.schedulers_online())
documents
|> Task.async_stream(
fn doc -> parse(doc, opts) end,
max_concurrency: max_workers,
timeout: :infinity,
ordered: true
)
|> Enum.reduce_while({:ok, []}, fn
{:ok, {:ok, result}}, {:ok, acc} -> {:cont, {:ok, [result | acc]}}
{:ok, {:error, reason}}, _acc -> {:halt, {:error, reason}}
{:exit, reason}, _acc -> {:halt, {:error, "Task failed: #{inspect(reason)}"}}
end)
|> case do
{:ok, results} -> {:ok, Enum.reverse(results)}
error -> error
end
end
@doc """
Parse multiple markdown documents using Rust-side parallelization.
Uses Rust rayon for parallel processing. Best for processing large documents
or when you want to minimize Elixir process overhead.
"""
@spec parse_batch_rust([String.t() | binary()], parse_options()) :: {:ok, [parse_result()]} | {:error, String.t()}
def parse_batch_rust(documents, opts \\ []) when is_list(documents) do
opts = Keyword.merge(default_options(), opts)
Native.parse_batch_parallel(documents, opts)
end
@doc """
Stream parse markdown documents with backpressure control.
Efficiently processes large numbers of documents with controlled memory usage.
"""
@spec parse_stream(Enumerable.t(), parse_options()) :: Enumerable.t()
def parse_stream(documents, opts \\ []) do
opts = Keyword.merge(default_options(), opts)
max_workers = Keyword.get(opts, :max_workers, System.schedulers_online())
Task.async_stream(
documents,
fn doc -> parse(doc, opts) end,
max_concurrency: max_workers,
timeout: :infinity,
ordered: false
)
|> Stream.map(fn
{:ok, {:ok, result}} -> {:ok, result}
{:ok, {:error, reason}} -> {:error, reason}
{:exit, reason} -> {:error, "Task failed: #{inspect(reason)}"}
end)
end
@doc """
Extract word count from markdown text with SIMD optimization.
Fast word counting using SIMD pattern matching for whitespace detection.
"""
@spec word_count(String.t() | binary()) :: {:ok, non_neg_integer()} | {:error, String.t()}
def word_count(content) when is_binary(content) do
Native.word_count_simd(content)
end
@doc """
Extract all links from markdown text.
Uses SIMD-optimized pattern matching to quickly find markdown link patterns.
"""
@spec extract_links(String.t() | binary()) :: {:ok, [link()]} | {:error, String.t()}
def extract_links(content) when is_binary(content) do
Native.extract_links_simd(content)
end
@doc """
Extract all headings from markdown text.
Fast heading extraction using SIMD pattern matching for hash characters.
"""
@spec extract_headings(String.t() | binary()) :: {:ok, [heading()]} | {:error, String.t()}
def extract_headings(content) when is_binary(content) do
Native.extract_headings_simd(content)
end
@doc """
Extract all code blocks from markdown text.
Efficiently finds fenced code blocks with language detection.
"""
@spec extract_code_blocks(String.t() | binary()) :: {:ok, [code_block()]} | {:error, String.t()}
def extract_code_blocks(content) when is_binary(content) do
Native.extract_code_blocks_simd(content)
end
@doc """
Extract all task items from markdown text.
Finds task list items with completion status detection.
"""
@spec extract_tasks(String.t() | binary()) :: {:ok, [task()]} | {:error, String.t()}
def extract_tasks(content) when is_binary(content) do
Native.extract_tasks_simd(content)
end
@doc """
Get performance statistics from the Rust processing layer.
Returns metrics about SIMD operations, cache hits, memory usage, and processing times.
"""
@spec get_performance_stats() :: {:ok, map()} | {:error, String.t()}
def get_performance_stats do
Native.get_performance_stats()
end
@doc """
Reset performance statistics counters.
Useful for benchmarking and performance analysis.
"""
@spec reset_performance_stats() :: :ok
def reset_performance_stats do
Native.reset_performance_stats()
end
@doc """
Clear pattern cache to free memory.
The pattern cache stores frequently used markdown patterns for faster processing.
"""
@spec clear_pattern_cache() :: :ok
def clear_pattern_cache do
Native.clear_pattern_cache()
end
# Private functions
defp default_options do
[
parallel: false,
max_workers: System.schedulers_online(),
cache_patterns: true,
track_performance: true
]
end
# ——— Diff API (composed) ———
@doc """
Compute a combined patch between two markdown strings.
Produces a `MarkdownLd.Diff.Patch` with changes from:
- Block-level diff (insert/delete/update, with inline ops for text blocks)
- JSON-LD semantic diff (add/remove/update triples)
Options:
- `:from_rev` / `:to_rev` — optional revision identifiers; defaults to content hashes
- `:similarity_threshold` — inline update coalescing threshold (default: 0.5)
- `:meta` — optional patch metadata map
"""
@spec diff(String.t(), String.t(), keyword()) :: {:ok, MarkdownLd.Diff.Patch.t()}
def diff(old_text, new_text, opts \\ []) when is_binary(old_text) and is_binary(new_text) do
from_rev = Keyword.get(opts, :from_rev, content_rev(old_text))
to_rev = Keyword.get(opts, :to_rev, content_rev(new_text))
meta = Keyword.get(opts, :meta, %{})
sim = Keyword.get(opts, :similarity_threshold, 0.5)
block_changes = MarkdownLd.Diff.Block.diff(old_text, new_text, similarity_threshold: sim)
jsonld_changes = MarkdownLd.JSONLD.diff(old_text, new_text)
changes = block_changes ++ jsonld_changes
{:ok, MarkdownLd.Diff.patch(from_rev, to_rev, changes, Map.merge(%{message: "diff"}, meta))}
end
defp content_rev(text) do
:crypto.hash(:sha256, text) |> Base.encode16(case: :lower)
end
end