Skip to content
Open
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
136 changes: 136 additions & 0 deletions PERFORMANCE_ANALYSIS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# DictationBridge Core Performance Analysis Report

## Executive Summary

This report documents multiple performance inefficiencies identified in the dictationbridge-core codebase. The analysis focused on memory allocation patterns, string operations, polling mechanisms, and system call usage across the C++ Windows application components.

## Critical Performance Issues Identified

### 1. Repeated Dynamic Memory Allocations in Hot Paths (HIGH PRIORITY)

**Location**: `inproc/ipc.cpp` - Lines 29, 52
**Impact**: Every text insertion/deletion event triggers heap allocation
**Description**: The `SendTextInsertedEvent` and `SendTextDeletedEvent` functions allocate memory dynamically for every IPC message, causing:
- Unnecessary heap fragmentation
- Memory allocation overhead in hot paths
- Increased latency for dictation events

```cpp
// Current inefficient code:
BYTE* data = new BYTE[cbData]; // Line 29
// ... use data ...
delete[] data; // Line 41
```

**Recommendation**: Use stack-allocated buffers for typical message sizes with heap fallback for large messages.

### 2. Memory Allocations in Message Processing (HIGH PRIORITY)

**Location**: `master/main.cpp` - Lines 53, 67, 75
**Impact**: Every WM_COPYDATA message triggers multiple allocations
**Description**: The main window procedure allocates temporary buffers for text and command processing:

```cpp
WCHAR* text = new WCHAR[cchText + 1]; // Lines 53, 67
char* command = new char[cds.cbData + 1]; // Line 75
```

**Recommendation**: Pre-allocate buffers or use stack allocation for typical sizes.

### 3. Client Command Memory Allocation (MEDIUM PRIORITY)

**Location**: `client/main.cpp` - Line 21
**Impact**: Every command sent requires dynamic allocation
**Description**: The `DB_SendCommand` function allocates memory for every command:

```cpp
auto data = new BYTE[cbData];
```

**Recommendation**: Use stack buffer for typical command sizes.

### 4. Inefficient String Length Operations (MEDIUM PRIORITY)

**Location**: Multiple files
**Impact**: Repeated string length calculations
**Description**: Multiple unnecessary calls to string length functions:
- `client/main.cpp:20` - `strlen(command)` could be cached
- `inproc/userhooks.cpp:25` - `wcslen(pszText)` in hot path
- `inproc/userhooks.cpp:160,206` - `wcslen()` on constant strings
- `inproc/dragon.cpp:33` - `_tcslen()` in loop condition

**Recommendation**: Cache string lengths and use compile-time constants where possible.

### 5. Inefficient Polling and Sleep Patterns (MEDIUM PRIORITY)

**Location**: `inproc/main.cpp` - Lines 87, 197, 202, 212
**Impact**: Fixed sleep intervals cause unnecessary delays
**Description**: Multiple locations use fixed sleep intervals:
- `Sleep(250)` - Line 87
- `Sleep(127)` - Lines 197, 202, 212

**Location**: `loader/main.cpp` - Lines 16, 35
**Impact**: Busy waiting with GetTickCount()
**Description**: The `nap()` function and main loop use inefficient timing mechanisms.

**Recommendation**: Use exponential backoff or event-driven approaches instead of fixed intervals.

### 6. Redundant System Calls (LOW-MEDIUM PRIORITY)

**Location**: `inproc/userhooks.cpp`
**Impact**: Repeated expensive system calls in hook functions
**Description**: Multiple hook functions perform similar operations:
- Repeated `GetClassName()` calls
- Repeated `GetWindowThreadProcessId()` calls
- Repeated `QueryFullProcessImageName()` calls

**Recommendation**: Cache results or consolidate validation logic.

### 7. Text Buffer Allocations in User Hooks (MEDIUM PRIORITY)

**Location**: `inproc/userhooks.cpp` - Lines 39, 47
**Impact**: Multiple allocations for text processing
**Description**: The `BeforeSendMessageFromDragon` function performs multiple allocations:

```cpp
LPWSTR pszAllText = new WCHAR[cchAllText + 1]; // Line 39
pszText = new WCHAR[cchText + 1]; // Line 47
```

**Recommendation**: Use single allocation or stack buffers for typical text sizes.

## Performance Impact Assessment

### High Impact Issues
1. **IPC Memory Allocations** - Affects every dictation event (highest frequency)
2. **Message Processing Allocations** - Affects all text insertion/deletion events

### Medium Impact Issues
3. **String Length Operations** - Cumulative overhead across multiple call sites
4. **Polling Patterns** - Affects responsiveness and CPU usage
5. **Client Command Allocations** - Affects command processing latency

### Low Impact Issues
6. **Redundant System Calls** - Affects hook performance
7. **User Hook Allocations** - Affects Dragon NaturallySpeaking integration

## Recommended Implementation Priority

1. **Phase 1 (Critical)**: Fix IPC memory allocations in `inproc/ipc.cpp`
2. **Phase 2 (High)**: Optimize message processing in `master/main.cpp`
3. **Phase 3 (Medium)**: Cache string lengths and optimize polling patterns
4. **Phase 4 (Low)**: Consolidate system calls and optimize remaining allocations

## Testing Strategy

Since no existing test suite was found, validation should focus on:
1. Functional testing - Ensure dictation still works correctly
2. Memory profiling - Verify reduced allocation frequency
3. Performance benchmarking - Measure latency improvements
4. Stress testing - Verify stability under high dictation load

## Conclusion

The most critical performance issue is the repeated memory allocations in the IPC layer (`inproc/ipc.cpp`), which affects every text insertion and deletion event. Implementing stack-based buffers with heap fallback for this component alone should provide significant performance improvements with minimal risk.

The identified issues represent opportunities for substantial performance gains, particularly in memory allocation overhead and system responsiveness during active dictation sessions.
166 changes: 101 additions & 65 deletions inproc/ipc.cpp
Original file line number Diff line number Diff line change
@@ -1,65 +1,101 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <stdio.h>
#include <tchar.h>
#include <windows.h>

#include "ipc.h"

HWND GetMasterWindow()
{
return FindWindowEx(HWND_MESSAGE, nullptr, TEXT("DictationBridgeMaster"), nullptr);
}

bool IsMasterRunning()
{
return GetMasterWindow() != nullptr;
}

void SendTextInsertedEvent(HWND hwnd, LONG startPosition, LPCWSTR text, LONG cchText)
{
HWND hwndMaster = GetMasterWindow();
if (hwndMaster == nullptr)
{
return;
}
DWORD cbData = (sizeof(DWORD) * 2) + (cchText * sizeof(WCHAR));
BYTE* data = new BYTE[cbData];
DWORD tmp = (DWORD)((__int64)hwnd & 0xffffffff);
memcpy(data, &tmp, sizeof(tmp));
tmp = (DWORD)startPosition;
memcpy(data + sizeof(DWORD), &tmp, sizeof(tmp));
memcpy(data + sizeof(DWORD) * 2, text, cchText * sizeof(WCHAR));
COPYDATASTRUCT cds;
cds.dwData = 0;
cds.lpData = (PVOID) data;
cds.cbData = cbData;
DWORD_PTR result;
SendMessageTimeout(hwndMaster, WM_COPYDATA, 0, (LPARAM) &cds, SMTO_NORMAL, 1000, &result);
delete[] data;
}

void SendTextDeletedEvent(HWND hwnd, LONG startPosition, LPCWSTR text, LONG cchText)
{
HWND hwndMaster = GetMasterWindow();
if (hwndMaster == nullptr)
{
return;
}
DWORD cbData = (sizeof(DWORD) * 2) + (cchText * sizeof(WCHAR));
BYTE* data = new BYTE[cbData];
DWORD tmp = (DWORD)((__int64)hwnd & 0xffffffff);
memcpy(data, &tmp, sizeof(tmp));
tmp = (DWORD)startPosition;
memcpy(data + sizeof(DWORD), &tmp, sizeof(tmp));
memcpy(data + sizeof(DWORD) * 2, text, cchText * sizeof(WCHAR));
COPYDATASTRUCT cds;
cds.dwData = 2;
cds.lpData = (PVOID) data;
cds.cbData = cbData;
DWORD_PTR result;
SendMessageTimeout(hwndMaster, WM_COPYDATA, 0, (LPARAM) &cds, SMTO_NORMAL, 1000, &result);
delete[] data;
}
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <stdio.h>
#include <tchar.h>
#include <windows.h>

#include "ipc.h"

HWND GetMasterWindow()
{
return FindWindowEx(HWND_MESSAGE, nullptr, TEXT("DictationBridgeMaster"), nullptr);
}

bool IsMasterRunning()
{
return GetMasterWindow() != nullptr;
}

void SendTextInsertedEvent(HWND hwnd, LONG startPosition, LPCWSTR text, LONG cchText)
{
HWND hwndMaster = GetMasterWindow();
if (hwndMaster == nullptr)
{
return;
}
DWORD cbData = (sizeof(DWORD) * 2) + (cchText * sizeof(WCHAR));

const DWORD STACK_BUFFER_SIZE = 1024;
BYTE stackBuffer[STACK_BUFFER_SIZE];
BYTE* data;
bool useHeap = cbData > STACK_BUFFER_SIZE;

if (useHeap)
{
data = new BYTE[cbData];
}
else
{
data = stackBuffer;
}

DWORD tmp = (DWORD)((__int64)hwnd & 0xffffffff);
memcpy(data, &tmp, sizeof(tmp));
tmp = (DWORD)startPosition;
memcpy(data + sizeof(DWORD), &tmp, sizeof(tmp));
memcpy(data + sizeof(DWORD) * 2, text, cchText * sizeof(WCHAR));
COPYDATASTRUCT cds;
cds.dwData = 0;
cds.lpData = (PVOID) data;
cds.cbData = cbData;
DWORD_PTR result;
SendMessageTimeout(hwndMaster, WM_COPYDATA, 0, (LPARAM) &cds, SMTO_NORMAL, 1000, &result);

if (useHeap)
{
delete[] data;
}
}

void SendTextDeletedEvent(HWND hwnd, LONG startPosition, LPCWSTR text, LONG cchText)
{
HWND hwndMaster = GetMasterWindow();
if (hwndMaster == nullptr)
{
return;
}
DWORD cbData = (sizeof(DWORD) * 2) + (cchText * sizeof(WCHAR));

const DWORD STACK_BUFFER_SIZE = 1024;
BYTE stackBuffer[STACK_BUFFER_SIZE];
BYTE* data;
bool useHeap = cbData > STACK_BUFFER_SIZE;

if (useHeap)
{
data = new BYTE[cbData];
}
else
{
data = stackBuffer;
}

DWORD tmp = (DWORD)((__int64)hwnd & 0xffffffff);
memcpy(data, &tmp, sizeof(tmp));
tmp = (DWORD)startPosition;
memcpy(data + sizeof(DWORD), &tmp, sizeof(tmp));
memcpy(data + sizeof(DWORD) * 2, text, cchText * sizeof(WCHAR));
COPYDATASTRUCT cds;
cds.dwData = 2;
cds.lpData = (PVOID) data;
cds.cbData = cbData;
DWORD_PTR result;
SendMessageTimeout(hwndMaster, WM_COPYDATA, 0, (LPARAM) &cds, SMTO_NORMAL, 1000, &result);

if (useHeap)
{
delete[] data;
}
}