Could be that you are talking about something else? It is not so high, try this:
?- length(L,10), maplist(=(abc),L), atomic_list_concat(L,',',R).
L = [abc, abc, abc, abc, abc, abc, abc, abc, abc|...],
R = 'abc,abc,abc,abc,abc,abc,abc,abc,abc,abc'.
Now do some measurement, absolutely it pays off already after N=2:
?- between(1,3,N), length(L,N), maplist(=(abc),L),
time((between(1,100000,_), atomic_list_concat(L,',',_), fail; true)), fail; true.
% 200,000 inferences, 0.031 CPU in 0.031 seconds (101% CPU, 6400000 Lips)
% 200,000 inferences, 0.016 CPU in 0.022 seconds (70% CPU, 12800000 Lips)
% 200,000 inferences, 0.031 CPU in 0.031 seconds (100% CPU, 6400000 Lips)
true.
?- between(1,3,N), length(L,N), maplist(=(abc),L),
time((between(1,100000,_), atom_list_concat(L,',',_), fail; true)), fail; true.
% 300,000 inferences, 0.000 CPU in 0.000 seconds (?% CPU, Infinite Lips)
% 600,000 inferences, 0.047 CPU in 0.053 seconds (88% CPU, 12800000 Lips)
% 900,000 inferences, 0.109 CPU in 0.100 seconds (109% CPU, 8228571 Lips)
true.
Below is the naive atom_list_concat/3 with O(N^2) atom_list_concat/3 complexity,
because N times O(N) complex of a naive atom_concat/3. You might get lower
complexity if atom_concat/3 uses some tricks, like Ropes, etc… or special help via
realloc() that avoids too much copying if not necessary, but the later would
also require identifying an atom accumulator by the Prolog compiler. Not sure
whether SWI-Prolog or some other Prolog system implements/uses that?
% atom_list_concat(+List, +Atom, -Atom)
atom_list_concat([X|L], D, A) :-
atom_list_concat(L, X, D, A).
% atom_list_concat(+List, +Atom, +Atom, -Atom)
atom_list_concat([], A, _, A).
atom_list_concat([X|L], Y, D, A) :-
atom_concat(Y, D, Z),
atom_concat(Z, X, T),
atom_list_concat(L, T, D, A).
Edit 17.04.2022:
There are different approaches to get non-naive atom_split(-,+,+)
that is O(N). Most of
the algorithms avoid the unnecessary copying in naive atom_concat/3. There is one
algorithm which goes two pass through the list of atoms and precomputes the
resulting atom length and then allocates a string, which it then populates. Another
algorithm is one pass and it uses a buffer which has amortizised low complexity
concering the extension of this buffer. I think SWI-Prolog uses the one pass
approach, but I don’t know exactly what kind of buffer it is. I have realized a kind
of two pass approach for Dogelog Player and prototyped a little bit the use of
atom_split/3 for all kind of stuff. It has native support by JavaScript and Python.
And since yesterday I have the one pass approach in formerly Jekejeke Prolog.
Maybe you don’t need this all if you have some non-naive atom_concat/3. But
I even banned atom_concat/3. You can bootstrap it, in the mode (+,+,-) as follows:
atom_concat(X, Y, Z) :-
atom_split(Z, '', [X,Y]).