UTF-8 is everywhere in modern applications. APIs, JSON documents, web requests, log files, messaging systems, and configuration files commonly use UTF-8 to represent text.

For many applications, text processing is not the main performance concern. But in services that handle a large amount of network or file data, encoding and decoding can become part of the CPU workload.

This becomes particularly interesting on Arm64 systems. Arm64 servers and cloud instances are widely used for workloads where power efficiency and price-to-performance are important considerations.

.NET has continued to improve text and encoding performance across supported architectures. Improvements in UTF-8 processing can benefit applications that spend significant time converting between UTF-8 bytes and .NET strings or working directly with UTF-8 data.

The important question is not simply whether "UTF-8 is faster." The useful question is:

Which UTF-8 operations benefit, and will your application actually notice the difference?

Why UTF-8 Performance Matters

A .NET string stores text as UTF-16.

External systems, however, commonly exchange text as UTF-8 bytes.

For example, an HTTP request may contain:

Hello, .NET

as UTF-8 encoded bytes.

At some point, an application may need to convert those bytes into a .NET string:

string text = Encoding.UTF8.GetString(data);

The reverse operation is also common:

byte[] data = Encoding.UTF8.GetBytes(text);

This gives us a basic flow:

UTF-8 bytes
    |
    v
UTF-8 decoder
    |
    v
.NET string
    |
    v
Application logic
    |
    v
UTF-8 encoder
    |
    v
UTF-8 bytes

If an application performs this conversion frequently, the encoding layer can become part of the performance profile.

What Is Arm64?

Arm64 refers to the 64-bit architecture based on the ARM instruction set.

It is used across:

From a .NET developer's perspective, the important point is that the same C# application can run on different processor architectures.

For example:

C# application
      |
      v
.NET runtime
      |
      +---- x64 native code
      |
      +---- Arm64 native code

The JIT compiler generates native code appropriate for the target architecture.

That means runtime optimizations can take advantage of CPU capabilities available on Arm64 systems.

Why UTF-8 Can Benefit from CPU Optimizations

UTF-8 processing often involves scanning bytes and checking whether they represent:

A straightforward implementation can process these values one at a time.

Modern CPUs can process multiple pieces of data at once using vector instructions.

This general technique is known as SIMD, or Single Instruction, Multiple Data.

Instead of thinking:

byte 1 -> check
byte 2 -> check
byte 3 -> check
byte 4 -> check

optimized code may process a group of bytes together.

The exact implementation depends on the runtime and hardware.

Developers usually do not need to write SIMD instructions themselves for standard Encoding.UTF8 operations.

.NET Handles the Low-Level Details

A major benefit of using the .NET libraries is that the application does not need to manually implement UTF-8 encoding.

For example:

using System.Text;

string message = "Hello, UTF-8";

byte[] data = Encoding.UTF8.GetBytes(message);

and:

string decoded = Encoding.UTF8.GetString(data);

The library and runtime handle the low-level encoding work.

When .NET improves those implementations for a particular architecture, existing application code can benefit.

This is one reason runtime upgrades can improve performance without requiring source-code changes.

ASCII Is an Important Case

A large percentage of text used by software systems contains ASCII characters.

For example:

GET /api/products
Content-Type: application/json
Authorization: Bearer ...

ASCII characters are represented directly in UTF-8 using one byte.

That makes ASCII-heavy input relatively straightforward to process.

Consider:

string json = """
{
    "name": "Laptop",
    "status": "active"
}
""";

The characters in this JSON document are mostly within the ASCII range.

UTF-8 processing can take advantage of this predictable representation.

Applications processing primarily ASCII text may therefore behave differently from applications that process large amounts of multilingual text.

Multilingual Text Is Different

UTF-8 also supports characters from many writing systems.

For example:

string text = "Hello नमस्ते 世界";

Some of these characters require multiple bytes in UTF-8.

This means the encoder and decoder need to perform more work than they do for plain ASCII.

A UTF-8 performance test should therefore include representative input.

Testing only:

Hello World

does not tell you how the system will behave with multilingual data.

Encoding with a String

A common application pattern is:

using System.Text;

public static byte[] Encode(string value)
{
    return Encoding.UTF8.GetBytes(value);
}

This is simple and appropriate for many applications.

However, it creates a byte array containing the encoded data.

If the application is repeatedly converting large strings, those allocations can become important.

For example:

for (int i = 0; i < 100_000; i++)
{
    byte[] data = Encoding.UTF8.GetBytes(message);

    Send(data);
}

The encoding operation is only part of the cost.

The application is also allocating a new byte array on each iteration.

This is why a performance investigation should consider both CPU time and memory allocation.

Working Directly with UTF-8 Data

Modern .NET also provides APIs that allow developers to work more directly with UTF-8 data.

For example, ReadOnlySpan<byte> can represent an existing UTF-8 buffer:

ReadOnlySpan<byte> utf8Data = buffer;

int length = utf8Data.Length;

This avoids creating another view of the data.

A parser can then inspect the bytes directly.

For example:

int comma = utf8Data.IndexOf((byte)',');

if (comma >= 0)
{
    ReadOnlySpan<byte> firstValue = utf8Data[..comma];
}

This is useful when the application does not need to immediately convert the complete input into a managed string.

UTF-8 and JSON

JSON processing is a good example of where UTF-8 matters.

A web service commonly receives JSON over HTTP:

{
  "id": 10,
  "name": "Laptop",
  "active": true
}

The data arrives as bytes.

A serializer then needs to parse those bytes and create the corresponding .NET representation.

For high-throughput services, avoiding unnecessary conversions can be useful.

For example, APIs that can consume UTF-8 data directly can avoid some intermediate conversion work.

The exact benefit depends on the serializer and application design.

The important idea is:

Network bytes
     |
     v
UTF-8 JSON
     |
     v
JSON parser
     |
     v
.NET objects

There is not always a reason to first convert the entire payload to a string.

A Practical API Example

Consider a minimal API endpoint receiving JSON.

A typical application may let the framework and serializer handle the request:

app.MapPost("/orders", async (Order order) =>
{
    await SaveOrder(order);

    return Results.Ok();
});

The developer does not need to manually decode the request body.

The framework and serializer handle the byte-to-object processing.

If the underlying runtime and libraries improve their UTF-8 handling, the application can benefit without changing this endpoint.

This is an important advantage of using high-level .NET APIs.

When UTF-8 Optimization Actually Matters

UTF-8 performance becomes more interesting when the application processes a large amount of text.

Examples include:

For a small administrative application, the difference may be difficult to notice.

For a service processing large volumes of text continuously, even relatively small improvements in CPU-intensive operations can become relevant.

UTF-8 on Arm64 vs x64

It is tempting to assume that Arm64 is automatically faster or slower than x64.

That is not a useful way to evaluate application performance.

The result depends on:

The same application can show different results on different processors.

A proper comparison should therefore use the actual environments where the application will run.

Benchmarking UTF-8 Operations

A simple benchmark can measure encoding:

using BenchmarkDotNet.Attributes;
using System.Text;

public class Utf8Benchmark
{
    private readonly string text =
        "Hello, .NET UTF-8 performance";

    [Benchmark]
    public byte[] Encode()
    {
        return Encoding.UTF8.GetBytes(text);
    }
}

You can create additional benchmark methods for decoding:

private readonly byte[] data =
    Encoding.UTF8.GetBytes("Hello, .NET UTF-8 performance");

[Benchmark]
public string Decode()
{
    return Encoding.UTF8.GetString(data);
}

When comparing Arm64 and x64, run the same benchmark on both architectures.

Keep the following consistent where possible:

Benchmark Different Types of Text

A useful UTF-8 benchmark should not use only one input.

For example:

ASCII

"Hello World 12345"

European Characters

"Bonjour café résumé"

Indian Languages

"नमस्ते दुनिया"

East Asian Characters

"こんにちは 世界"

Mixed Input

"Hello नमस्ते 世界"

Different inputs exercise different UTF-8 encoding and decoding paths.

This is particularly important if the production workload contains multilingual data.

Avoid Unnecessary String Conversions

Suppose an application receives UTF-8 bytes and only needs to check whether a particular byte exists.

Converting everything into a string first may be unnecessary:

string text = Encoding.UTF8.GetString(data);

if (text.Contains("ERROR"))
{
    // ...
}

Depending on the actual requirement, a byte-oriented parser may be more appropriate.

However, this does not mean byte processing should replace normal string APIs everywhere.

If the application genuinely needs Unicode-aware text operations, converting to a .NET string may be exactly the right choice.

Use the representation that matches the operation.

Common Mistakes

Assuming Every UTF-8 Operation Gets the Same Improvement

Encoding and decoding are different operations, and their behavior depends on the input.

Do not generalize from one benchmark.

Ignoring Allocations

This code:

byte[] data = Encoding.UTF8.GetBytes(text);

creates a byte array.

If it runs repeatedly, allocation behavior needs to be considered alongside CPU performance.

Testing Only ASCII

ASCII-heavy input can produce very different results from multilingual text.

Use representative production data when benchmarking.

Comparing Different Machines

Running Arm64 on one machine and x64 on another does not provide a controlled comparison.

Hardware differences can influence the results.

Optimizing Before Profiling

If your application spends most of its time waiting for a database, optimizing UTF-8 encoding is unlikely to change overall request latency significantly.

Best Practices

1. Use Standard .NET Encoding APIs

Prefer:

Encoding.UTF8

and the related APIs instead of implementing UTF-8 encoding yourself.

2. Avoid Unnecessary Conversions

If data is already available as UTF-8 bytes and the operation can be performed safely on the byte representation, consider processing it without converting the entire payload.

3. Use Spans for Hot Paths

For performance-sensitive buffer processing, ReadOnlySpan<byte> and related APIs can reduce unnecessary copying.

4. Benchmark on the Target Architecture

If production runs on Arm64, test on Arm64 hardware.

Do not assume x64 results represent Arm64 behavior.

5. Include Realistic Text

Use production-like data, including multilingual text if the application supports it.

6. Measure Allocation and CPU Together

A faster encoding operation may still create substantial allocations.

Look at the complete cost.

Advantages and Disadvantages

Advantages

Disadvantages

Existing .NET APIs can benefit from runtime improvements

Benefits depend on workload and CPU

Arm64 can take advantage of architecture-specific optimizations

Results may differ between Arm64 processors

UTF-8 is efficient for ASCII-heavy text

Multibyte characters require additional processing

Span-based APIs can reduce unnecessary copying

Low-level byte processing increases code complexity

Useful for high-throughput services

Often not important for small applications

Troubleshooting UTF-8 Performance

If UTF-8 processing appears in your application's performance profile, start with the actual operation.

Check whether the application is spending time in:

Encoding.UTF8.GetBytes()
Encoding.UTF8.GetString()
JSON parsing
String allocation
Buffer copying

Then check allocations.

If the problem is allocation rather than CPU time, changing the runtime alone may not solve the issue.

You may need to change how buffers are managed.

If CPU usage is the main problem, compare the same workload on the target architecture and runtime version.

When Should You Use UTF-8 APIs Directly?

Direct UTF-8 handling is most useful when:

For normal business logic, it is usually better to let .NET frameworks and libraries handle encoding.

For example:

public record Customer(
    int Id,
    string Name);

There is no reason to manually manage UTF-8 bytes simply because the underlying HTTP request uses UTF-8.

The framework already handles that work.

Summary

UTF-8 processing is an important part of many modern .NET applications, especially services that handle HTTP, JSON, messaging, files, logs, and other text-heavy workloads.

On Arm64, runtime and library optimizations can make common UTF-8 operations more efficient by taking advantage of the capabilities of the target processor. Developers can benefit from these improvements through standard .NET APIs without writing architecture-specific code.

The biggest practical point is that UTF-8 performance depends on the workload. ASCII text, multilingual text, encoding, decoding, allocation, and JSON parsing can all behave differently.

For most applications, continue using the standard .NET APIs and focus on clean code. If profiling shows that text processing is a real bottleneck, then benchmark the actual workload, measure allocations, and test on the Arm64 hardware used by the application.

The runtime should handle the low-level optimization. Your job is to make sure the application is not doing unnecessary conversions or allocations in the first place.