See the question and my original answer on StackOverflow

Most of the time used seems to be caused by .NET's Process class (Access Denied exception legitimately thrown, etc.), so here is a full P/Invoke version that doesn't use it but uses the native CreateToolhelp32Snapshot function:

[DllImport("advapi32", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, out IntPtr TokenHandle);

[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);

[DllImport("kernel32", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr CreateToolhelp32Snapshot(int dwFlags, int th32ProcessID);

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool Process32First(IntPtr hSnapshot, ref PROCESSENTRY32 lppe);

[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool Process32Next(IntPtr hSnapshot, ref PROCESSENTRY32 lppe);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct PROCESSENTRY32
{
    public int dwSize;
    public int cntUsage;
    public int th32ProcessID;
    public IntPtr th32DefaultHeapID;
    public int th32ModuleID;
    public int cntThreads;
    public int th32ParentProcessID;
    public int pcPriClassBase;
    public int dwFlags;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public string szExeFile;
}

public static List<List<string>> GetProcessWithUsers()
{
    var result = new List<List<string>>();
    const int TH32CS_SNAPPROCESS = 2;
    var snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    var entry = new PROCESSENTRY32();
    entry.dwSize = Marshal.SizeOf<PROCESSENTRY32>();
    if (Process32First(snap, ref entry))
    {
        do
        {
            const int PROCESS_QUERY_LIMITED_INFORMATION = 0x00001000;
            var handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, entry.th32ProcessID);
            result.Add(new List<string> { entry.szExeFile, GetProcessUser(handle) });
            if (handle != IntPtr.Zero)
            {
                CloseHandle(handle);
            }
        }
        while (Process32Next(snap, ref entry));
    }
    CloseHandle(snap);
    return result;
}

public static string GetProcessUser(IntPtr handle)
{
    if (handle == IntPtr.Zero)
        return null;

    const int TOKEN_QUERY = 0x0008;
    if (!OpenProcessToken(handle, TOKEN_QUERY, out var tokenHandle))
        return null;

    var wi = new WindowsIdentity(tokenHandle);
    CloseHandle(tokenHandle);
    return wi.Name;
}

On my PC, I've been down from 1500 ms to 30 ms (x50).