from experience, i commonly use each of them (plus pack) depending on the context... if you're looking for pure computer efficiency, a wild guess would be that sprintf is the most close to low level programming hence possibly the most efficient, but not sure at all
... my guess is that sprintf and combine are the least efficient of the bunch, as they have to convert Max atoms into C strings, assemble them into one string and (in the case of sprintf without symout) break that string down into individual atoms.
prepend, append, join, pack and pak all share the same basic mechanism, by operating upon atoms.
that being said, prepend and append, being simpler, are probably slightly faster than pak and pack (which, I guess, are exactly the same, except maybe for one more if statement in pack). join looks quite simple as well, but maybe not as much as prepend/append... and append probably needs less message selector adjustments than prepend, on average...
... but then I wonder how relevant this is w/r/t the actual building of patches. I mean, sprintf is probably less efficient than the others, but how likely are we to notice the slightest difference? and also, if using sprintf saves us a couple of objects (say, a trigger and a [zl reg]) that will probably compensate for the intrinsic efficiency gap...
the only important thing, in my experience, is avoiding creating too many different symbols - although it takes an awful lot to actually see the patch slow down! In this sense, up with pack, pak, join, down with combine. but of course ymmv...
Well, my problem with Max for two years or more is that my patcher for live electronics has reached such a level of complexity that most of the time I can't find "obvious" things to worry about to solve issues. In my experience the single rule which always proved to be correct to make my patcher more reliable and responsive is: every improvement, even tiny, must be considered. And sometimes such improvements were not obvious at all.
Max is a fantastic toolbox and the people at Cycling '74 have made an incredible job with this program since it was created by Miller Puckette. However sometimes I feel like trying to buil houses and having good bricks and cement to achieve this but with a lack of help when it comes to architecture or engineering.
Just remember that "good" and "fast" architectures are not necessarily (and sometimes cannot be) the same thing. A good architecture is easy to build with and expand on. "Build it right, then make it faster by finding bottlenecks" is a good rule of thumb IMO. (That along with "use poly~ for that"...)
In my limited experience it seems that efficient patching is more of an issue than object speed per se. Those (atom-based) objects all seem to transmit data at about the same rate.. it's so close that I suspect other variables are influencing the small differences that exist.
And while this is probably obvious to most experienced users, one thing I have learned is to use hot right inlets only when necessary. My initial impression of pak was "why the heck would anyone want to use pack". That extra bang always seemed inefficient. Well, when you are packing a dozen elements into a list and don't really need the intermediate changes at the other end, that's incredibly inefficient. So with pak (or join @triggers -1) the sword cuts both ways… they can be more efficient or less efficient, depending on the context.