I think this is pretty simple and efficient:
julia> nt1 = (; a = 1, b = 2)
(a = 1, b = 2)
julia> nt2 = (; b = 3, a = 4)
(b = 3, a = 4)
julia> NamedTuple{keys(nt1)}(nt2)
(a = 4, b = 3)
julia> @code_warntype NamedTuple{keys(nt1)}(nt2)
MethodInstance for (NamedTuple{(:a, :b)})(::NamedTuple{(:b, :a), Tuple{Int64, Int64}})
from (NamedTuple{names})(nt::NamedTuple) where names in Base at namedtuple.jl:99
Static Parameters
names = (:a, :b)
Arguments
#self#::Type{NamedTuple{(:a, :b)}}
nt::NamedTuple{(:b, :a), Tuple{Int64, Int64}}
Body::NamedTuple{(:a, :b), Tuple{Int64, Int64}}
1 ─ %1 = Core.apply_type(Base.NamedTuple, $(Expr(:static_parameter, 1)), Tuple{Int64, Int64})::Core.Const(NamedTuple{(:a, :b), Tuple{Int64, Int64}})
│ %2 = Base.getfield(nt, 2)::Int64
│ %3 = Base.getfield(nt, 1)::Int64
│ %4 = %new(%1, %2, %3)::NamedTuple{(:a, :b), Tuple{Int64, Int64}}
└── return %4
It’s implemented in Base with a generated function.