FindFirst is definitively the slowest. It needs at least two calls (+ FindClose), and maintain some state in-between.
GetFileAtributes() is the fastest under Windows, and fpaccess/euidaccess on POSIX systems.
Note that a fallback to FileAge() is needed in case of sharing violation:
function FileExists(const FileName: string): Boolean;
{$IFDEF MSWINDOWS}
// use GetFileAttributes: much faster than original, which uses FileAge=FindFirst
var Attr: Integer;
LastError: Cardinal;
begin
Attr := Integer(GetFileAttributesW(pointer(FileName)));
if Attr <> -1 then
Result := Attr and FILE_ATTRIBUTE_DIRECTORY = 0 else begin
LastError := GetLastError;
Result := (LastError <> ERROR_FILE_NOT_FOUND) and
(LastError <> ERROR_PATH_NOT_FOUND) and
(LastError <> ERROR_INVALID_NAME) and
// (use FileAge to test SHARE_EXCLUSIVE files)
((LastError = ERROR_SHARING_VIOLATION) or (FileAge(FileName)<>-1));
end;
end;
{$ENDIF}
{$IFDEF LINUX}
begin
Result := euidaccess(PChar(FileName), F_OK) = 0;
end;
{$ENDIF}
(extracted from our Enhanced RTL source code)
But the version currently included in Delphi 10.3 Rio SysUtils.pas is just as fast on Windows.
On POSIX, it uses stats, which is slower than euidaccess().