Plugin Idea: Enabling Multi-Core Rendering for Obsidian – Overcoming Single-Core Limitation to Enhance Performance for Lists, Canvases, and Windows

Hey everyone!

As I mentioned in this post,

Obsidian currently only uses a single core for processing, and GPU acceleration provides little to no improvement in its rendering performance, and considering that the official team hasn’t made any clear statements about turning it into a multi-core application or using GPUs to accelerate rendering, I’ve been wondering if it’s possible to use a plugin to enable Obsidian to utilize multiple cores for rendering lists, canvas cards and notes, as well as windows. So, I asked AI to help me sketch out a simple solution. Here’s the idea:

The Problem:

  • Single-core bottleneck: Obsidian currently handles rendering tasks (such as file rendering, canvas updates, and list processing) on a single CPU core, which causes lag and slowdowns, especially with larger datasets.
  • Multiple open windows/canvases: When working with multiple windows, complex canvases, or large lists, performance degrades since Obsidian can’t utilize the full potential of multi-core CPUs.

The Solution: Multi-Core Rendering Plugin

The idea is to create a plugin that enables Obsidian to process content in parallel using multiple CPU cores. By doing so, it would improve the performance of tasks like:

  • File Rendering: Assigning different files to different cores for quicker rendering.
  • Canvas Rendering: Splitting the canvas into smaller tasks, with each card or note being handled by a different core.
  • List Rendering: Breaking long lists into chunks and processing each chunk on a separate core to load the content faster.

How It Would Work:

The plugin would leverage worker threads to offload tasks and run them on separate CPU cores. Here’s how it could work for different components:

  1. File Rendering: Each file opened in Obsidian would be assigned its own worker thread. This would allow files to be rendered independently, so if you’re working with multiple files, the CPU can handle them simultaneously.
  2. Canvas Rendering: For canvases with many interconnected notes or cards, the plugin would assign each card or note to a separate worker thread. This would ensure smooth interaction with the canvas, even when it’s large or complex.
  3. List Rendering: Long lists in Obsidian (especially when you’re dealing with hundreds or thousands of items) could be split into smaller chunks. These chunks would be processed by different worker threads, enabling faster list rendering without blocking the main thread.

Example Code for the Plugin:

obsidian-multicore-render
│
├── main.ts           # The main entry file for the plugin, listens to windows, files, canvases, and lists
├── workers
│    ├── renderFileWorker.js  # Worker thread for rendering files
│    ├── renderCanvasWorker.js  # Worker thread for rendering canvas cards/notes
│    └── renderListWorker.js    # Worker thread for rendering lists
└── package.json      # Plugin metadata file


Here’s a basic breakdown of how the plugin might look:

Plugin Entry (main.ts):

import { Plugin } from 'obsidian';
import { Worker } from 'worker_threads';

export default class MultiCoreRenderer extends Plugin {
  private fileWorkers: Map<string, Worker> = new Map();
  private canvasWorkers: Map<string, Worker> = new Map();
  private listWorkers: Worker[] = [];

  async onload() {
    console.log('Loading multi-core rendering plugin...');

    // Listen for file changes
    this.registerEvent(this.app.workspace.on('file-open', (file) => {
      if (file) this.assignFileWorker(file.path);
    }));

    // Listen for canvas changes
    this.registerEvent(this.app.workspace.on('canvas-changed', (canvas) => {
      if (canvas) this.assignCanvasWorker(canvas.id);
    }));

    // Listen for list changes in the editor
    this.registerEvent(this.app.workspace.on('editor-change', (editor) => {
      const content = editor.getValue();
      const listItems = content.match(/^- .+/gm) || [];
      this.splitListForMultiCoreRendering(listItems);
    }));
  }

  // File rendering: Assign a separate worker for each file
  assignFileWorker(filePath: string) {
    if (this.fileWorkers.has(filePath)) return;
    const worker = new Worker('./workers/renderFileWorker.js', { workerData: { filePath } });
    this.fileWorkers.set(filePath, worker);
    worker.on('message', (result) => {
      console.log(`File ${filePath} rendering complete`, result);
    });
  }

  // Canvas rendering: Assign a separate worker for each card/note
  assignCanvasWorker(canvasId: string) {
    if (this.canvasWorkers.has(canvasId)) return;
    const worker = new Worker('./workers/renderCanvasWorker.js', { workerData: { canvasId } });
    this.canvasWorkers.set(canvasId, worker);
    worker.on('message', (result) => {
      console.log(`Canvas ${canvasId} rendering complete`, result);
    });
  }

  // List rendering: Split the list into multiple tasks and use multiple workers for processing
  splitListForMultiCoreRendering(listItems: string[]) {
    const chunkSize = Math.ceil(listItems.length / 4); // Assuming 4 cores are used
    const chunks = [];
    for (let i = 0; i < listItems.length; i += chunkSize) {
      chunks.push(listItems.slice(i, i + chunkSize));
    }

    chunks.forEach((chunk, index) => {
      const worker = new Worker('./workers/renderListWorker.js', { workerData: { chunk, index } });
      this.listWorkers.push(worker);
      worker.on('message', (result) => {
        console.log(`List rendering task ${index} complete`, result);
      });
    });
  }
}


renderFileWorker.js

const { parentPort, workerData } = require('worker_threads');

const filePath = workerData.filePath;

function renderFile(filePath) {
  // Simulate file rendering
  console.log(`Rendering file: ${filePath}`);
  // Simulate time-consuming task
  for (let i = 0; i < 1e8; i++) {} 
  return `Rendering complete: ${filePath}`;
}

const result = renderFile(filePath);
parentPort.postMessage(result);

renderCanvasWorker.js

const { parentPort, workerData } = require('worker_threads');

const canvasId = workerData.canvasId;

function renderCanvas(canvasId) {
  console.log(`Rendering Canvas: ${canvasId}`);
  // Simulate time-consuming task
  for (let i = 0; i < 1e8; i++) {}
  return `Rendering complete: ${canvasId}`;
}

const result = renderCanvas(canvasId);
parentPort.postMessage(result);

renderListWorker.js

const { parentPort, workerData } = require('worker_threads');

const { chunk, index } = workerData;

function renderListChunk(chunk) {
  console.log(`Rendering list chunk: ${index}`);
  // Simulate time-consuming task
  for (let i = 0; i < 1e7; i++) {}
  return `List chunk ${index} rendered, item count: ${chunk.length}`;
}

const result = renderListChunk(chunk);
parentPort.postMessage(result);

Why This Would Be Awesome:

  1. Faster Rendering : Tasks like file loading, canvas rendering, and list processing would be much faster since they’re being processed in parallel.
  2. Better Multi-Tasking : You could have multiple windows, canvases, and lists open and rendered simultaneously without lag.
  3. Optimized for Power Users : If you’re someone who works with large note collections, multiple windows, or complex visualizations, this plugin would drastically improve your workflow.
1 Like

Heavy calculations that cause the UI to freeze could be moved to web workers (and Obsidian already uses them, like in the word count core plugin). However, the suggested approach doesn’t quite make sense and feels more like LLM hallucinations. It’s also completely outside the scope of what a plugin can do.
Moreover, the real bottleneck is the efficiency of the code—focusing on not doing unnecessary work and doing it lazily, rather than eagerly—not heavy calculations.

I am going to close this thread as this is not plugin territory and a clear duplicate of the FR linked