Skip to content

Measuring text with TextMeasurer.Measure is slow #268

Open
@0xced

Description

Prerequisites

  • I have written a descriptive issue title
  • I have verified that I am running the latest version of Fonts
  • I have verified if the problem exist in both DEBUG and RELEASE mode
  • I have searched open and closed issues to ensure it has not already been reported

Description

I tried to use SixLabors.Fonts to compute widths of many different texts to produce a nice looking Excel file with Simplexcel. It turned out to be an unusable solution because it was too slow. I tracked the slowness down to the SixLabors.Fonts.TextMeasurer.Measure function.

I ran some benchmarks, comparing SixLabors.Fonts to SkiaSharp and here are the results.

BenchmarkDotNet=v0.13.1, OS=macOS Catalina 10.15.7 (19H1615) [Darwin 19.6.0]
Intel Core i9-9980HK CPU 2.40GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK=6.0.300
  [Host]    : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT
  MediumRun : .NET 6.0.5 (6.0.522.21309), X64 RyuJIT

Job=MediumRun  IterationCount=15  LaunchCount=2  
WarmupCount=10  
Method Text Mean Error StdDev
SixLaborsFonts Hello world 31,270.4 ns 621.99 ns 871.94 ns
SkiaSharp Hello world 539.0 ns 8.13 ns 11.92 ns
SixLaborsFonts The q(...)y dog [43] 110,846.6 ns 4,511.26 ns 6,752.24 ns
SkiaSharp The q(...)y dog [43] 1,421.4 ns 38.72 ns 55.54 ns
SixLaborsFonts a 7,025.6 ns 349.17 ns 511.81 ns
SkiaSharp a 353.3 ns 11.29 ns 16.90 ns

I eventually used SkiaSharp instead of SixLabors.Fonts and did not investigate further why measuring text is slow.

The benchmark below could help as a starting point, should someone tackle this issue (myself included if I ever find the time).

Steps to Reproduce

Run the following project with dotnet run -c Release

MeasureTextBenchmarks.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
    <PackageReference Include="SixLabors.Fonts" Version="1.0.0-beta17" />
    <PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.0" />
  </ItemGroup>

</Project>

Program.cs

using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using SixLabors.Fonts;
using SkiaSharp;

BenchmarkRunner.Run<MeasureTextBenchmark>();

[MediumRunJob]
public class MeasureTextBenchmark : IDisposable
{
    private readonly TextOptions _textOptions;
    private readonly SKTypeface _arialTypeface;
    private readonly SKFont _font;
    private readonly SKPaint _paint;

    public MeasureTextBenchmark()
    {
        const string fontFamilyName = "Arial";
        const int fontSize = 16;

        _textOptions = new TextOptions(SystemFonts.Get(fontFamilyName).CreateFont(fontSize, FontStyle.Regular));

        _arialTypeface = SKTypeface.FromFamilyName(fontFamilyName, SKFontStyle.Normal);
        _font = new SKFont(_arialTypeface, fontSize);
        _paint = new SKPaint(_font);
    }

    public void Dispose()
    {
        _arialTypeface.Dispose();
        _font.Dispose();
        _paint.Dispose();
    }

    [Params("a", "Hello world", "The quick brown fox jumps over the lazy dog")]
    public string Text { get; set; } = "";

    [Benchmark]
    public void SixLaborsFonts() => TextMeasurer.Measure(Text, _textOptions);

    [Benchmark]
    public void SkiaSharp() => _paint.MeasureText(Text);
}

System Configuration

  • Fonts version: 1.0.0-beta17
  • Other Six Labors packages and versions: none
  • Environment (Operating system, version and so on): macOS 10.15.7
  • .NET Framework version: .NET 6.0.5
  • Additional information: N/A

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions