howto count lines - fast

H. S. Teoh via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Fri Jun 2 10:32:12 PDT 2017


On Thu, Jun 01, 2017 at 08:39:07AM +0100, Russel Winder via Digitalmars-d-learn wrote:
> On Wed, 2017-05-31 at 16:37 -0700, H. S. Teoh via Digitalmars-d-learn
> wrote:
> > 
> […]
> > With D, we can have the cake and eat it too.  The understandable /
> > naïve implementation can be available as a fallback (and reference
> > implementation), with OS-specific optimized implementations guarded
> > under version() or static-if blocks so that one could, in theory,
> > provide implementations specific to each supported platform that
> > would give the best performance.
> 
> But isn't that the compiler's job?

Unfortunately, the compiler can only go so far, because it doesn't
understand the larger context of what you're trying to accomplish.
Modern optimizing compilers certainly go a lot further than before, but
still, at some point some amount of human judgment is needed.

Also, compiler implementors do still have to do the "heroics", or
rather, teach the compiler to do the "heroics" when compiling
straightforward code. So while the general programmer probably will have
less need for it, compiler writers still need to know how to do them in
order to write the optimizing compilers in the first place.


> The lesson of functional programming, logic programming, etc. (when
> the acolytes  remember the actual lesson) is that declarative
> expression of goal is more comprehensible to people than detailed
> explanation of how the computer calculates a result. Object-oriented
> computing lost the plot somewhere in the early 2000s.

There is no argument that straightforward code is more comprehensible to
people.  The question here is whether it delivers maximum performance.

We know from Kolgomorov complexity theory that global optimization, in
the general case, is undecidable, so no optimizing compiler is going to
be able to generate optimal code in all cases. There will always be
cases where you have to manually tweak it yourself.  Of course, that
doesn't mean you go around micro-optimizing everything -- the usual
approach is to write it the straightforward way first, then profile it,
identify the hotspots, and find ways to improve performance in the
hotspots. Well, at a higher level, the first order of business is really
to look at it from an algorithmic POV and decide whether or not a
different algorithm ought to be used (and no optimizing compiler can
help you there).  Then if that's still not enough, then you dig into the
details and see if you can squeeze more juice out of your current
algorithms -- if the profiler has indicated that they are the
bottleneck.


> The advance of Scala, Kotlin, Groovy, and now Rust and Go (but only to
> some extent), which D has, is to express algorithms as declarative
> requirements in a dataflow manner.
> 
> One day hardware people will catch up with the hardware innovations of
> the 1970s and 1980s regarding support of dataflow rather than state.

Dataflow can only capture a limited subset of algorithms.  Of course, in
my observation, 90% of code being written today generally belongs in the
category of what I call "linear shuffling of data", i.e., iterate over
some linear set of objects and perform some transformation X on each
object, copying linear memory region X to linear memory region Y,
rearranging some linear set of objects, permuting the order of some
linear set of things, etc..  This category basically subsumes all of GUI
programming and web programming, which in my estimation covers 90% or
maybe even 95% of code out there.  The bulk of game code also falls
under this category -- they are basically a matter of copying X items
from A to B, be they pixels to be copied to the video buffer, traversing
the list of in-game objects to update their current position, direction,
speed, or traversing scanlines of a polygon to map a 3D object to 2D,
etc.. Almost all business logic also falls under this category. All of
these are easily captured by dataflow models.

However, there are algorithms outside of this category, that are not
easily captured by the dataflow model.  Solving certain graph theory
problems, for example, require actual insight into the structure of the
problem than mere "move X items from A to B".  Route planning, which is
an NP-complete problem that, for practical applications, can only be
approximated, and therefore actual thought is required for how you
actually go about solving the problem beyond "data X moves from A to B".
Computing the convex hull of a set of input points, used for solving
optimization problems, if expressed and solved in a naïve way, has
O(n^3) time complexity, and therefore impractical for non-trivial
problem instances.

True, your average general programmer may not even know what a convex
hull problem is, and probably doesn't even need to care -- at worst,
there are already libraries out there that implement the algorithms for
you.  But the point is, *somebody* out there needs to write these
algorithms, and they need to implement these algorithms in an optimal
way so that it will continue to be useful for non-trivial problem sizes.
You cannot just say, "here is the problem specification, now just let
the computer / AI system / whatever figure out for themselves how to
obtain the answer".  *Somebody* has to actually sit down and specify
exactly how to compute the answer, because generic ways of arriving at
the answer are exponential or worse and are therefore useless. And even
a feasible algorithm may require a lot of "heroics" in order to make
medium-sized problems more tractable.  You want your weather forecasting
model to produce an answer by tomorrow before the 10am news, not 3
months later when the answer is no longer relevant.


> > I disagree about the philosophy of "if you need to go faster, use a
> > bigger computer".  There are some inherently complex problems (such
> > as NP-complete, PSPACE-complete, or worse, outright exponential
> > class problems) where the difference between a "heroic
> > implementation" of a computational primitive and a naïve one may
> > mean the difference between obtaining a result in this lifetime vs.
> > practically never. Or, more realistically speaking, the difference
> > between being able to solve moderately-complex problem instances vs.
> > being able to solve only trivial toy instances.  When you're dealing
> > with exponential complexity, every small bit counts, and you can
> > never get a big enough computer.
> 
> There are always places for experimentation, and innovation. Hard
> problems will always be hard, we just need to find the least hard way
> of expressing the solutions.

Some problems are inherently hard, and no amount of searching can reduce
its complexity past a certain limit. These require extreme measures to
be even remotely tractable.


> The crucial thing is that people always work to remove the heroicism
> of the initial solutions, creating new computational models, new
> programming languages, etc. to do it.
[...]

But *somebody* has to implement those computational models and
programming languages.  If nobody knows how to write "heroic" code, then
nobody would know how to write an optimizing compiler that produces such
code either, and these computational models and programming languages
wouldn't exist in the first place.

I know that the average general programmer doesn't (and shouldn't) care.
But *somebody* has to, in order to implement the system in the first
place. *Somebody* had to implement the "heroic" version of memchr so
that others can use it as a primitive. Without that, everyone would have
to roll their own, and it's almost a certainty that the results will be
underwhelming.


T

-- 
Question authority. Don't ask why, just do it.


More information about the Digitalmars-d-learn mailing list