The following content is provided under a Creative
Commons license.
Your support will help MIT OpenCourseWare continue to
offer high-quality educational resources for free.
To make a donation, or view additional materials from
hundreds of MIT courses, visit MIT OpenCourseWare at
ocw.mit.edu.
PROFESSOR: Welcome to 6.172, Performance Engineering of
Software Systems.
This is a fun class.
It's all about going fast.
We all like to go fast--
well, not all of us.
But everybody in this class likes to go fast.
Otherwise you won't like the class very much.
It's all about making stuff go fast.
So a couple of brief announcements.
We're going to be videotaping for OpenCourseWare.
So if you don't want to be videotaped, you should sit it
in a place where the camera won't catch you.
Because we're in such a large classroom, I'd like everybody
to sort of every day, without me having to draw--
I'm not really an artist.
My sister's an artist, actually.
She's a painter.
But maybe that gene escaped me.
It went that way and not this way.
So I don't want to have to draw it up again.
So everybody, try to confine yourself to this sort of
quarter of the classroom.
And that way, it'll feel more like a size room that
we ought to be in.
Let me go to our course information sheet.
Did everybody get a copy of the
course information handout?
So we're going to the class-- we're going to start at the
beginning with the Administrivia stuff, try to
run through that as quickly as we can.
And then my co-lecturer, professor Saman Amarasinghe--
if you can't pronounce or spell his last name, you can
just say "Saman"--
will give today's lecture.
And we have four teaching assistants.
So teaching assistants, please stand up.
They are Josh, John, Reed, and Eric.
And let me tell you, what they have done in the last few days
to get us ready for this course is nothing short of
superhuman.
So you really need to accord them your respect, and they're
going to really work hard to help you folks do
well in this class.
We have, I think, a pretty decent ratio.
I think even with concerns about cutbacks, et cetera, to
have four TAs for this is really good.
And we need it, because it's a software class.
And software is, as you know, bugs can take an arbitrarily
long time to find.
You guys can sit.
Besides our teaching assistants, we have two
support staff people helping us, Mary McDavitt, who works
for Saman, and Marcia Davidson, who works for me.
And they're generally pretty good about being able to track
us down if we're in some far corner of
the world or something.
Our lectures are going to be held on Tuesdays and Thursdays
in this room, 2:30 to 4:00 PM.
You should plan to attend regularly.
We dispense all kinds of wonderful gems that you will
miss if you think you're going to get it just from reading
and even looking at videotapes or whatever.
I'm not sure what our arrangement is going to be
about having videos available.
OK, they're not going to be available, except unless
there's something really extenuating circumstance.
You have to go into a hospital to have your forebrain removed
or something--
something serious, OK?
All material in the lecture, even if it's delivered oral,
is fair game for projects and quizzes.
We will post the lecture slides, but they don't
generally convey all of the information.
And we're going to be
video-recorded, as I mentioned.
If you don't want to appear, sit behind the camera, and you
won't appear.
We have no recitations in this class.
However, the teaching assistants will periodically
hold primers where we will bring you up to speed on
various technologies that you may not be up to speed in,
such as C. How many people have programmed in C before?
And how many have not programmed in C before?
A few.
OK.
Tools such as VTune.
Who's used VTune before?
Who has not used VTune?
Good.
Pin-- who's used Pin?
OK.
Who has used Cilk++?
OK, a couple people.
So you see, we're going to have some primers to bring you
up to speed in those kinds of things.
We don't expect that you came in knowing any of those
things, particularly.
We're going to be using Stellar for our course
management.
And here's the URL.
And you should make sure that you're registered as a member
of 6.172, because otherwise, you're not going to get any
announcements.
We're going to do all of our
administration through Stellar.
So make sure that you're registered there.
At the end of today, I think it is, we'll send out an
announcement to the student mailing list.
If you don't get something by the end today--
so you wake up tomorrow morning, you have no email
from the course staff.
What that means is you should email us.
AUDIENCE: Well, that mailing list would be the
preregistered ones.
PROFESSOR: That'd be the preregistered ones.
And then that way, we'll get all the people.
So if you know you're not preregistered, you can send us
email before then.
There are two one-hour quizzes given during class time.
AUDIENCE: One and a half.
PROFESSOR: What's that?
AUDIENCE: One and a half hour.
PROFESSOR: Oh, you're right.
It's one and a half hour quizzes.
Yes, two one and a half hour quizzes.
We'll correct that in the online version.
Given during class time.
Can somebody, by the way, correct this as we go along?
Great.
The dates are on the course calendar, which is available
on Stellar.
We've had mixed results with the ICS feed from Stellar, but
you're supposed to be able to download the calendar to your
iPhone or other ICS-capable devices.
But we were successful in downloading, but all the times
were screwed up.
But anyway, hopefully, maybe they'll allow that.
But anyway, the calendar is there.
The quizzes will generally be closed book and closed notes,
but you will be permitted crib sheets, one sheet that you
summarize anything that you feel that you need to know.
Generally, everything that we cover in here is fair game for
the quizzes, including things that are in
prerequisite courses.
There is no final exam because we have a final project.
This is a fast-moving class, so if you get caught behind,
it's not a good sign, because it's really hard to work
double-hard to keep up in this class.
You really have to keep on top of things.
And so for that reason and others--
because it's really difficult for us--
it's fast moving for us as well--
we generally cannot accept late projects.
If you haven't been able to complete a project, you should
just package up your partial solution and hand that in, and
you'll get partial credit on it.
Do not just say, oh, I'm not going to hand something in,
and expect it to get it done later.
If you do find yourself in an unusual situation which you
think there are some extenuating circumstances,
like the aforementioned brain surgery or something, let us
know in advance.
So if you're going to get in a car accident, it's really
helpful if you tell us the day before so that we can plan.
Obviously can't always do that, but if it is something
for which there is any reasonable expectation it
should be done beforehand, you should let us know.
And we may require, for those kinds of exceptions, some kind
of confirmation from a dean or medical professional.
So if that's the route that you're in, you can plan a
little bit and make sure that those confirmations
are on their way.
The grading, this is essentially approximately how
we're going to grade.
As you can see, there's a bunch of content in the
projects and a bunch in the quizzes.
And then there's also participation grade.
And the participation is both in--
we will have design reviews and such, so that's part of
the participation, as well as asking
questions and such in class.
And because we're video-recording everything, we
know exactly who's asking questions, unless you choose
to sit outside the video zone.
In addition, if there is a significant missing element to
your work, it doesn't matter how well you did on the other
things, you will receive a failing grade.
You must, essentially, do all the work.
So if you do not get substantial credit on the
final project or any other two assignments, for example, you
can expect to receive a failing grade.
It goes downhill very quickly.
If you're missing one assignment, generally don't
expect to get higher than a C, for example.
It goes downhill very, very quickly, because that's what
this is all about is hands on.
What's permissible in this class?
So it's your responsibility to satisfy both the letter and
spirit of the rules.
So if you have any questions, let us know.
Here's what the rules are.
Also, let me just say how Saman and I treat this.
We have been involved in cheating incidents off and on
over the years.
I've been at MIT almost 30 years.
Saman, you've been here--
PROFESSOR: 13.
PROFESSOR: 13 years.
So we've been here long time, longer than you folks.
We've been through this a bunch of times.
It's really a pain when somebody cheats.
It robs me of a huge amount of my time that I could otherwise
be spending on teaching and students and my own
professional development.
However, we both take cheating incidents extremely seriously.
So we have this thing that is sometimes really inconvenient
called integrity.
And I'd rather, if I find somebody's cheating, just give
you an F and not deal with it.
Drat.
Instead, when somebody's cheating, I tend to want to
take it to the Committee on Discipline, where you face
things like expulsion, et cetera.
So I don't like to do that.
It's very time consuming for me.
But I find it hard to just look the other way, because I
view the issue of integrity as protecting the vast majority
of students who are obeying the rules.
So when a cheating incident occurs, I sort of feel honor
bound to push it to its proper conclusion.
So please don't cheat.
We will use a variety of means at our disposal, including
technological ones, to detect cheating.
Also, if you think that sharing your results with
somebody else means that they're
cheating and you're not--
in some cultures that's the way it's treated--
no.
In this culture, in particular the culture in this classroom,
they're both equally guilty of academic dishonesty, whether
you are the giver or the taker.
Equally, we treat that as equal offense.
So here's the particular things.
You may not share, generally, your solutions with people who
are not in your group.
So we will have a bunch of group projects,
generally in pairs.
And that includes both people in the class and people
outside the class.
So generally, you keep things to yourself.
When you're in a group, of course, you can share ideas
within the group.
But we expect that everybody's making a fair contribution.
So you should be concerned if you are not holding up your
end of your group participation.
And we're going to ask you to describe the contributions of
the people in your group.
Generally, the group, as I say, is two.
So it's basically saying, what did you do?
How did you divide up your project?
What did you do?
What did the other person do?
You generally can't share it with anybody else.
You can't copy or transcribe solutions from other places.
The work you submit must be your own.
Pretty straightforward.
You may go out and use general conceptual material, such that
you might get from a good textbook or from Wikipedia or
someplace like that.
That's perfectly fine, resources you would ordinarily
have available.
But if you do use any material from an external source, you
should properly cite it in normal academic that fashion.
Cite where it is so that somebody else can go and find
and look to see what it is that you're citing.
If you feel that you have transgressed in
some way, let us know.
It always goes way easier.
I generally tend to be kind of a nice guy when somebody comes
to me saying, oops, verses I hear from somewhere else or
one of our tools detects something else.
Then I'm kind of cranky.
So I'd rather, if you come to me, we could work something
out, generally.
Most of your out-of-class time will spent
completing six projects.
And there's an outline of what the projects are.
They're all fun projects.
You can ask the TAs.
The TAs will tell you how much fun they are, because they've
generally helped to design and built their own
solutions to them.
Some will be done in pairs, some individually.
The final project can either be done in pairs or in threes,
I think we said.
AUDIENCE: [INAUDIBLE]
PROFESSOR: Yes, we also have-- we didn't put that in here.
We also have a Project 0, which will be available today
after class.
So you should go to Stellar and get Project 0.
Project 0 is not to be handed in.
It's getting warmed up with all of the machines and tools
that we'll be using in the class.
We have been very fortunate to have very nice contributions
from Dell and Intel of a new Cluster facility that has 16
12-core machines.
And they're Intel Core i7 machines.
Each chip has six cores, and there's two cores per machine,
each with 48 gigabytes of memory.
So these are kind of honking modern facility.
So as I say, it's fun.
It's fun when you do something on your laptop, and then you
go and you do it on one of these machines and
it goes like this.
It's really fun when these things go fast.
So Project 0, you should start on today.
And it's basically the handout called Getting Started.
It will be available on Stellar.
And it will take you through getting access to these
machines and so forth.
In particular, in order to use these machines, you must have
a CSAIL account.
Who does not have a CSAIL account?
So that is going to be the first thing you need to do,
because you've got to get that.
So what you want to do is get
registered for a CSAIL account--
and we give instructions in there.
It's basically going to a web page--
pretty much as soon as you get out of the classroom.
Because you won't be able to start anything until you have
this CSAIL account.
And hopefully, we'll be able to give you access within a
few hours after you register.
Yeah, question?
AUDIENCE: What operating systems are the tools--
PROFESSOR: Linux.
We'll be using Linux.
So there are a variety of ways that we describe for how you
use these machines remotely.
We have the Andrew File System--
AFS--
which you can use so that you can edit things locally but
have an SSH terminal window on the machine, which makes it
very convenient.
On the other hand, for some people, often some dorm rooms
with poor connectivity, doing things by AFS may be
too slow for you.
But there are a variety of other ways that you can-- you
can actually edit right on the machine.
And there are a variety of ways of having a repository on
your machine, because we'll be using Git as
our versioning mechanism.
So you'd be able to have it there and be able to make
copies back on the other machine.
So there are a variety of ways to work.
And you're free to make the software run on your own
laptops or workstations.
The one thing I'll say is we will not support that.
We don't have the resources to support beyond using the
machines that we actually provide.
I think I've got to go faster here.
So let's see.
So software engineering.
So research in programmer productivity has demonstrated
that pair programming, where two programmers sit together,
one at the keyboard and one looking on, is actually faster
than two programmers coding separately.
Why do you suppose that is?
Yeah?
AUDIENCE: You don't get stuck.
The other person can tell you-- it's like when you're
[INAUDIBLE] writing on the blackboard, you get nervous.
You don't know what you're doing.
[INAUDIBLE]
PROFESSOR: Yeah, you could be stuck.
What else?
AUDIENCE: You don't procrastinate.
PROFESSOR: Yeah, you don't procrastinate.
That's a good one.
Don't procrastinate.
AUDIENCE: Finding bugs.
PROFESSOR: Yeah, finding bugs.
So the main one is finding bugs, that the person who's
looking on finds bugs.
In fact, I was just pair programming with Reed the
other day, and we were trying to figure out, why is this
thing not working?
And I say, gee, it looks fine.
It says, next y equals x.
And he says, no, next y is supposed to equal y.
And it's like, we had both read over that code, but as
soon as I spelled it out, I didn't even notice.
But of course, him in hearing it says, oh, it's supposed to
be x and y and y, not x and y.
So anyway, that kind of thing, you pick up very quickly.
It makes it very easy to debug.
Likewise, regression testing demonstrably promotes fast
co-development.
So this is where you build a battery of tests that includes
tests for every mistake that you've ever made.
And it can also be helpful to write unit tests, where you
test individual ones.
And you should also use tools like the assert package.
And some of these things will be taken through in Project 0.
The MIT POSSE.
We have recruited senior software engineers in the
region to share with you their invaluable knowledge and
experience.
We call them Masters In The Practice Of Software Systems
Engineering, which has the great acronym MIT POSSE.
These are people who are not being paid.
And they're senior.
They're doing this because they're volunteering, because
they want to help.
You have a lot to learn from these people, and we will have
code and design reviews with these people with your
assigned master.
You want to treat them nicely.
They're not doing this so that some snotty-nosed kid can say,
I don't care about you.
That's not what they're doing.
They're there to help you get a good grade.
And these are some really talented people, people who
are very famous in their own areas of expertise.
So be gracious, is the main thing I would say.
Be gracious.
Let's see.
The assignments.
You can read about the assignments.
Basically, generally, what we're going to do is have a
beta submission in which we're going to test everything.
And we're also going to take all your tests.
We're going to throw all the tests into a big pool and run
everybody's tests against everybody software.
You will lose points if you fail some of the tests,
assuming the tests are correct tests.
You will lose points if you have an incorrect test.
You will lose points if somebody passes your test and
they have a bug.
You will get points if your test picks up something that
very few other groups tested for.
So you have to both submit code and submit tests, which
is OK, because you've got to test your own stuff before you
hand it in.
But we're also going to be using it to test each other.
After beta, we will have a design and code review with
your master.
And let me just say, the master's there not affecting
your grade in any way.
They do give us reports about what happened in their
meeting, but they have no bearing on the grade.
They're just there to help.
So you don't have to view them as--
they're not part of the course staff.
If you want to get angry at somebody about how things are
done, get angry at Saman and me.
Don't get angry at them.
The final submission is generally due a couple weeks
after the beta.
And it's after you've gotten feedback on your design,
feedback on things.
We'll give you the whole battery of everybody's
regression tests, so you can test out your code against
everybody's regression test and use that.
And then generally, the scoring is such that people
who get fast code get high marks.
People who get slow codes get lower marks.
If you need help, [UNINTELLIGIBLE].
Speed is fun.
Enjoy the class.
Yeah, question?
AUDIENCE: In terms of the bug testing, I'm
thinking of bugs like--
I don't know--
[INAUDIBLE]
or something like that.
If you happen to not have made a bug in your own program
[INAUDIBLE]
would that count against you in the final testing.
PROFESSOR: You have to view your beta test as testing
other peoples' code also.
PROFESSOR: But if you [UNINTELLIGIBLE] a test that
finds bugs in somebody's program, and if very few
people wrote that test, then you will get points.
So you don't have to test for the world, everything.
If you fail somebody's test, but not yours, you will be
deducted points.
If your test finds other people's bugs, you will gain
points for that.
But on the other hand, if you don't have a test for--
what you said, I think you don't have to go and test for
every possible condition in there.
The negative side is, if you miss something,
we will deduct points.
Positive side is, if your test finds somebody else's bugs,
you'll get points for it.
We'll go into detail exactly how [UNINTELLIGIBLE].
Another thing we did last year, which was kind of
interesting--
I think there were mixed--
some people really liked it, some people
were not that happy--
is a little bit of competitive grading.
So here, what we are doing is not the full grade--
small part of the grade.
What you do is when you first submit your beta, rerun
performance tests of everybody.
And of course, one of you are going to get the
fastest code for that.
At that point, we are going to say, OK, this
is the fastest code.
And how [UNINTELLIGIBLE]
everybody else with that code?
And the person who gets fastest code for that part of
the grade gets the full points.
And everybody else will get a partial part of the points.
However, you get a chance in your final submission to
basically improve your code.
If you do better than the last time's best code, you won't
get any more additional points, because then we don't
want to create [? an arms race. ?]
So you at least had to match the previous
times to get full points.
So you get a chance to see how you stand against your peers
and get a chance to actually [UNINTELLIGIBLE] math with
your peers.
So first time we did it, we said, OK, it's the fraction of
your performance against the performance of the best code.
The problem was, there were a couple of codes that 1000x
performance improvement, and everybody got 10x.
So you've got, like, 0.25 grade.
That didn't help.
So now we actually are doing a logarithmic log scale.
So we'll get the fraction, and we'll take the log off that.
So there will be a little bit more equality.
So you will know how worse off you are, and you get a chance
to [UNINTELLIGIBLE].
Because it is very important, because a lot of times, there
might be a crucial thing that you missed.
And instead of trying to be, "aha, got you" in this class,
we are trying to give you a chance to actually learn.
So if you missed it, OK, you missed it the first time.
But you get the second chance to come and fix it and see
whether you can do well.
And of course, if you do better than the best grade,
there are bragging rights, but you don't get extra points.
With that, let me--
where are we?
So again, as Charles pointed out, I'm pretty excited about
this class.
This is a really fun class.
Unfortunately, I'm a little bit under the weather.
So I'm going to sit down.
And if I don't sound that enthusiastic, hopefully I'll
get enthusiastic later.
And that's one reason we ask you to come forward, because
we don't have to come here and shout.
So hopefully people in the back can here.
If you don't, still, there are some seats in the front.
You can move forward.
So for the rest of the class, what we want to do is go
through an example and look at why performance matters.
For that, I selected matrix multiply.
And I think your Project 0, you are going to also play
with matrix multiply.
And I think that you'll get a feel for what kind of things
will happened and how much room you have in
doing well in here.
And at the same time, as I go through this, you'll get a
feel for what's the process that you might be following
through the class and what kind of things--
because linear programming, after taking a bunch of
programming classes, probably, you have a methodology set up
in your mind, how to go about doing that.
And the same thing after this class, we want you to have a
methodology of going through and improving performance.
And this is basically a little bit of a contrite example, but
you'll get a feel.
So what's matrix multiply?
It's a very fundamental operation for many
computations.
So you are going to produce Matrix A by multiplying Matrix
B and C. So you want to calculate each value in here
to take a row of B and a column of C, and you get each
element of A. And you do that for every element of A. And
this is the possible simplest code you can write in a
language like C, a [? three-loop nest, ?]
and this matrix multiply.
So, so far, things should be pretty simple.
So what I did was I looked at matrix multiply and said,
look, you have taken software engineering class.
You have thought about object-oriented programming,
lot of nice techniques to build very
useful, portable software.
And let's try to write matrix multiply with
some of those concepts.
So the first thing I want to do is matrix multiply write
object oriented using immutable classes, and that,
in fact, I want matrix class to represent both
integers and doubles.
So here's my class structuring here.
And actually, I'm going to show you a bunch of code now,
next couple of slides.
I'm not expecting you to follow everything
instantaneously.
I'm going to go through somewhat faster.
But you have the slides.
So I always expect you to, perhaps, in some free time, to
go and look at some of the slides, get a little bit of
better feel for what happened.
So here's what matrix multiply does.
I have a value class that figures out the matrix type.
And if it's integer, it keep integers.
If it's a double, I keep a double.
And here, basically, do this value in here.
And then I have a matrix class.
Basically, given a matrix type and a matrix, it can create a
matrix here.
This basically creates your matrix in here by first
creating a bunch of rows here, and then instantiating each
row in here, whether it's integer or double, depending
on the type you want.
And then, of course, there are other things in matrix,
basically, in here.
And you can update a matrix.
This is going to be a little bit interesting.
I will give a little bit here, because what I'm going is I'm
doing immutable types.
That means I can't change anything in the matrix.
If you are updating, you have to create a new matrix.
So I'll show a little bit about how to
go about doing that.
And then there's this row class.
I'm going through this fast, because
it's not that important.
If you get a chance, go through this and get a feel
for what I'm doing.
But the idea is doing this very software engineering way
of implementing a matrix multiply.
So then here's my matrix multiply in here, that what
I'm doing is I'm updating each of these matrices by giving a
value of B and C and adding it to A and
updating A again in here.
So when I run matrix multiply, 1,024 by 1,024 matrix, this is
the time I got.
Actually, it took a long time, in fact.
And the key thing, is the performance good?
That's the first question to ask.
Because you run, you get a result.
When the result is correct, OK, great.
You get the correct result.
But how do you do?
And this is sometimes a very hard thing, to know that you
have a problem is itself a huge win--
that you figure out you have a problem.
And what I'm going to do is go through a little bit of a
calculation to see how we did it.
It took about five hours to do this matrix multiply, and that
doesn't look right already.
So if you think about matrix multiply, it's an N-cube
operation, basically, what matrix multiply is doing.
That's the algorithmic [? class. ?]
If you look at Charles' book, that's what it would say.
It's N-cube operation.
So this is the number of operations you want to do.
And for each matrix operation, you only do three index
updaters, add, a multiply, and a check--
probably six ops to do, basically.
And that means I need to do this many operations.
Basically, you can think about machine operations to do,
basically, because you have to read three values, update one,
do a multiply and add, and do a branch.
And if I look at that with my hours, I am doing this many
operations per second, which looks high.
Big number.
But my machine, actually, on my somewhat old PC, transfers
even much faster.
So if you think about it, I am taking about 8,300 cycles to
do any kind of a machine operation.
This doesn't look right.
So at this point, this is what I would call a "backup
[UNINTELLIGIBLE] calculation." I haven't proven anything.
This one worked well in the theory class, but if you can
just jot down a couple of these things, you can get a
feel for where you stand very fast.
And then you realize, OK, I am a couple of orders of
magnitude off here on what best I could do.
And that should give you a feel for what's going on.
OK, so what's going on here?
How can we improve performance?
Because we know we have a problem.
So what we can do is we can look deeply
into the program execution.
And at some points, OK, this program runs slow.
[UNINTELLIGIBLE]
time spent.
So we can figure out time spent, basically, things like
by method--
which method you spend most of the time running.
Sometimes by line.
And we will learn a bunch of profiling tools that will give
you this feel.
So instead of reading a million lines of code, looking
at every line in there, you can very fast converge onto
the culprits where most of the time being spent.
And that's a very easy way of triaging your problem.
And you look at things like time spent, cumulative time
spent within that and everything called underneath,
number of invocations of the functions.
And a lot times, you have a feel for what it should be.
And when you look at this, if it doesn't look right, you
say, wait a minute, this goes against my intuition.
Perhaps there's a problem.
So what you can do is, by doing this, you can
identify hot spots.
Basically, if 90% of the time it's it one routine, that's
what you want to fix.
And hopefully, that routine is doing something too slow.
Then you can actually get good performance in here.
So here's the profile I ran for a matrix multiply.
And interestingly, what I found was, most of the time
spent in basically creating doubles--
number of calls, time spent here, this much time was
spending, basically creating new values in here, this
initializing a double in here.
So I'm doing matrix multiply.
Why should I be initializing values?
So this is an issue.
And I have the most number of calls to that too.
So we can go look at it and say, hey, that would seem to
be a problem here.
So the problem here was kind of obvious.
What happened was I didn't use immutable [? class. ?]
So this is my matrix representation.
I have a bunch of pointers pointing to each row.
And now assume I won't update this element.
And of course, I can't modify the entire thing.
It's immutable.
So what I had to do is create a copy that updates.
And then, of course, I had to create a new matrix.
I don't have to copy everybody else, because these guys were
not changed.
So I can place a pointer to these things and create this
new matrix that points to these things.
And then, if you think about what's going on, in fact, and
this is my new matrix after I do that, I [? updated ?] two
end copies for each update, because what I had to do is I
had to copy this entire row up to get that.
And I had to create this entire index again for each
point in each row.
So just to update one element, I am creating two end copies.
So what that means is now our N cube matrix multiply ends up
being N to the power of 4 algorithm.
So suddenly, you realize, wait a minute, I'm doing something
bad in here.
I'm actually [UNINTELLIGIBLE]
N-cube algorithm, and the way I saw, it becomes n to the
power of 4 because I'm making all these copies in there.
So this is a bad thing to do, of course.
And copying is very costly, because you're making
duplicates in here.
And of course, you're creating a huge amount of garbage,
because you are just copying and leaving the previous value
for garbage collection.
So garbage collector's coming up all the time, and huge
memory footprint.
So this is no good.
And can we do better?
Hopefully, we can.
So the next thing we did was, OK, just get rid of these
immutable types.
That seemed to be a [UNINTELLIGIBLE]
here.
And then, I actually have--
I will go faster over the code, so if you are
interested, you can go look at the code in here.
So here, what we did was, still we have the issues like
object oriented and ability to represent both
integers and doubles.
So what that means is each matrix can be the integer
matrix or a double matrix.
So this is where you're instantiating with integers or
doubles in here.
So we do that.
And how much you think this can improve performance by
doing that?
In fact, this was pretty nice.
I actually got a 219x performance improvement
because I went from n to the power 4 algorithm to n to the
power 3 algorithm.
So here is interesting, algorithm has changed with it,
and we have a pretty nice performance improvement.
So if I look at this now, what I find is I'm spending a lot
of method time, I'm doing matrix multiply.
That's good.
But I'm spending all this time doing get
double and get methods.
I'm trying to do matrix multiply.
Why am I spending all this time trying to do
get double and get?
What I found was issue is the method call overhead.
Now, what happened is matrix row has two different matrix
types, the integer row and double row.
So every time I want to get a row, I don't know if I have
integer row or double row.
I have to do a look-up to figure out my type.
Am I working with the integer matrix or matrix in doubles?
And this is what we call "dynamic dispatch." So instead
of knowing exactly the methods we are to go, I had to
actually look up, OK, which method I'm supposed to go.
I have to do that since, if the matrix is integer, I might
have to call a different method than matrix is double.
I have to check that.
And then that call, instead of direct call,
becomes indirect branch.
I have to check that and basically branch on that.
And these indirect branches are costly, because what that
means is I don't where I am going until I do that test.
So no machine--
we'll go into details and pipelines and
stuff as we go on.
What that means is normally, a machine can have a lot of
instruction in flight.
So that means that I have to know a lot more information to
get the instructions going.
But here, until you resolve the instructions, you can't do
the next one.
So it's called a, basically, pipeline stall.
So instead of having hundreds of instructions going,
basically, suddenly I can't do anything until the previous
instruction is done.
So the machine slows down a lot because of this.
Normally, what you can do is keep fetching next
instructions here.
We have to fetch, test, then the test result is available,
then that's when you can fetch the next instruction.
Let me go through that file.
So direct batch means target address is known.
You can fetch ahead of the targets/ If I know where I am
going, even if I don't get there, I can fetch ahead of
the target.
I can go start fetching what happens next ahead, basically,
into [? the branch. ?]
Sometimes, you can even fetch both directions, or the
direction you might always go might get
fetched by the architect.
We'll talk about a lot of architecture details in the
future lectures.
Indirect branch means until I calculate the address, I have
no idea where I'm going.
That address is only calculated.
And because of that, hardware slows down like crazy.
So this seems to be not a good thing to have these integers
and doubles instead of having one nice class of just
[UNINTELLIGIBLE]
might be nice.
I just basically create a double class.
It's all matrices are doubles.
If I want integers, I will just rewrite my matrix class
or copy and replace.
Now my matrix class becomes much more simpler.
This is what I got, and this is my row in here.
And voila, minute I do that, I basically went up about a 2.4x
performance improvement, because now I don't have to
keep doing that.
And altogether now, I have improved my performance by
about 500x here.
And for ops per cycle, we have gone down from
8,000 to 38 to 16.
So we do still better.
And now we look at, again, my profile data.
And what I see is, OK, I am spending a lot of time in
matrix multiply.
That's OK.
But also, I am spending time in this matrix get method.
I am trying to get each, basically, row and data
elements of the matrix.
So here is the summary of these things.
I'm going to just keep that one, because I gave you
[INAUDIBLE].
So the problem in this object-orientedness is I
[? handle ?] each row is represented separately--
nice objects.
These objects overload the memory.
And in fact, if I want to get to my row, I have to appoint a
[? chase ?]
to go get that.
It's not contiguous in memory.
And to get to every row, I have to actually go through
this [? point ?] indexing in here.
[UNINTELLIGIBLE] matrices are one, nice, simple objects.
So we can say, OK, wait a minute.
This method call overhead actually keeps dominating.
And because I have done this nice object-oriented thing in
here, so I can get to the object-oriented file here and
say, OK, get to the objects.
And I write this nice loop of [UNINTELLIGIBLE].
And I have two-dimensional matrices in here.
And voila, I got another 2.2x performance
improvement in here.
OK, now I am down to about seven cycles for each
operation in here.
That's good.
So what can we do here?
So we did all of these things in Java, because it's supposed
to be a nice language.
But Java has a lot of overhead.
It's like you do memory-bound checks.
You have to do this bytecode interpreter
and stuff like that.
On the other hand, C can run much faster.
You don't have to do any kind of memory-bound checks.
The compilation happens before you run, so it is not part of
your cost of doing anything.
So this can work very much like C. You can just almost
cut and paste.
And if you know, C is [? index chain, ?]
[? and this ?]
to C is just changing a few lines.
And I did that, and this is, basically, the C code in here.
This is the matrix multiply.
This is the kind of allocation in here.
C can be a little bit painful to allocate.
This is my matrix multiply.
It will look similar.
And I got 2.1x just by going from Java to C. This, I was
surprised, because that piece of Java code looked very much
close to C. But by just doing that, I actually got another
2.1x performance gain.
Now this is kind of the high-level stuff.
Now we have to go to nitty-gritty
details in the hardware.
So in modern hardware, there are these things called
"performance counters" that hardware [UNINTELLIGIBLE]
that we can look at what's happening inside hardware.
We can get a feel for what happens inside hardware.
And there are things called CPI, which clock cycles per
instruction.
So that means if the instructions are running very
slow, you will have multiple clock cycles per instructions.
That means instructions are stalling.
Something is wrong.
Cache misses.
So data [UNINTELLIGIBLE]
[? pay attention to ?]
caches and stuff like that.
If data are supposed to be in cache and have cache hits all
the time, but if you are getting cache misses,
something is wrong.
Accesses are not good.
And then also the instructions retired.
That means, how many instructions are being
executed and retired?
If you are doing too much work, the number of
instructions retired will go high in here.
This will give you a few how many [? work been done. ?]
So if you look at this one, I have a CPI of 4.
That means for every instruction, need about almost
close to five clock cycles, which is not good.
I have pretty high cache miss rate, too--
L1 cache miss rate and some L2 cache miss rate.
This shows how many instruction are in what they
call [? vector in the ?] instruction form called SSE
instructions.
And here's the number of instructions got retired.
So we just use that baseline case.
So one thing I can look at was my cache miss rates seem to be
pretty darn high.
What might be happening here?
So if you look at matrix multiply--
so I have Matrix A in here and Matrix B--
what happens is, to calculate this value in [? Matrix A ?]
here, basically, I'm going to go through this row in here,
and I'm going through column in here.
Problem with the column is it's all [? low in ?]
the cache.
This is your linear memory.
So because this is here two dimensional, but inside your
hardware, there's no two-dimensional memory.
You have a linear memory, so this is your linear memory.
This is how the memory look like.
And this is how the memory look like.
So each row and column is in these areas.
But here, to calculate this value, this is nice.
I go through memory that is contiguous.
But C, I am jumping all over my memory.
And jumping on memory is not that great cache behavior.
And contiguous can do much better, because what you do
is, when you get a cache, you get a cache line that has more
than one word.
So in here, what you're doing is you are getting large cache
lines only using one word before you go to the next one.
So that's not great in cache behavior.
So what you can do is you can make this memory contiguous.
So what you can do is, in matrix multiply, normally, n
to the cubed computation to the squared data.
So to help on n to the cubed, it's OK to do some n to the
squared processing.
That means you can do a data transport before you start
matrix multiply.
Do some pre-processing.
So this will also come in handy.
A lot of these problems you'll do, you'll say, wait a minute,
can I do a pre-process that's cheaper that will really
improve my main processing?
This is a theme that comes again and again.
And then we can get this n to the cubed running faster since
this n to the cubed is much larger than n to the squared.
You can amortize the cost of doing that.
And basically, to get that, we can transpose some matrix
going into the squared operations.
And then now n to the cubed might run much faster.
And in fact, what I have done here is do the transpose of
this matrix.
And now we run this one, and then everything will be in
contiguous memory.
And voila, I got another 3.4x performance improvement.
Now I am running almost at one cycle per op,
which is really nice.
But modern superscalars actually can do better, so we
can actually end up being even better.
So let's see.
So we are doing that.
So here is the interesting thing.
Now I look at my transpose [? spot. ?]
So if you look at that number of retired instructions, it's
more or less the same.
Number of SSE instructions, more or less the same.
I have a 2x improvement in my L1 cache miss rate.
But more than that, I have a 5x improvement in the CPI.
That means my instructions are stalling a lot
less now than before.
So this is where I got all my performance [UNINTELLIGIBLE],
because my instructions are running much faster.
So by looking at data--
this is the kind of things you're
learning during the class--
then you can get a feel for what actually is happening and
why you are getting that.
You can go do more in the cache, in memory system.
So remember, the memory system, if you're
[UNINTELLIGIBLE], if you look at cache, what they're doing
is we have a small amount of memory called cache that are
very fast access.
And you have a large amount of memory that is sitting out
that has slow access.
So what you want to give user feel as if they have a lot of
memory, everything is very fast access.
So hardware does a lot of crazy things to give you the
illusion that you have very fast memory and you have a
huge amount of that.
Of course, that's not true, because you have these caches.
And what happens is, if you do things wrong,
these get to slow down.
So in the cache system, you have cache [UNINTELLIGIBLE]
You have each multicore going to its own L1 cache that goes
L2 cache, which goes to L3 cache, which goes to the main
memory system.
And you want all of the data to be here.
If that's the case, things run very fast.
If not, you have to go here.
It will be a lot more slow, here, even
slower, here, very slow.
So the key thing is to get the data as much as possible here.
So here's the kind of cycles, basically.
One cycle to get here-- three cycles.
If you go to L2 to L3, you have to do 14 cycles
[UNINTELLIGIBLE].
If you go to L3 to main memory, you might sometimes
get hundreds of cycles delaying here,
so it's very costly.
So the caches are very temperamental beasts.
They work beautifully, but when they don't work, these go
really haywire.
So a lot of times, if there's a performance issue, look at
the cache miss rate and say, aha, the
caches are not behaving.
And can I help in here?
So the interesting way to look at that is
how much data I touch.
These graphics didn't show up.
Let me explain.
So if you do matrix multiply, I am trying to calculate one
value in here.
So this is one value.
I have to calculate row and the column.
This slide got screwed up a little bit.
So just look at this calculating [? row bar. ?]
So if I want to calculate a row of values here, what I
have to do is I have to get this row in B, and I have to
use entire C to calculate one row of A. That means I have
touched this much data items just to get one row of A. So
that means I am calculating 1,024 values of A. I am
touching this much data items to get that.
On the other hand, if I do block matrix multiply, that
means I calculate, basically, a block in here.
This block is also 32 by 32.
It has 1,024 elements in here.
But I only need a block here and block here.
Voila, I only have looked at 25,000 elements.
I got the same number of output calculated.
But I have only touched much less elements altogether to
calculate that.
Do you see that?
And nice thing is, if this thing fits in the cache, I am
much better than this might not fit in the cache.
So I can calculate blocks in matrix multiply by looking at
a lot less number of data, that has a good chance at
fitting in cache-- and of course, when you calculate.
you make sure it fits in cache--
than trying to just calculate row by row.
So one thing you can do is--
this is a lot of common things we do.
There's many ways to get same results.
You can change the execution order.
You can change the algorithm.
You contain data structures.
And some of them actually might do
better than the other.
And here, we can actually say we can do the execution of the
change and make sure we are only looking at small amount
of data, you get the same number of data calculated.
And of course, sometimes you have to be careful, because if
you do things in different order, you might not get the
exact same results.
So if you are a numerical [UNINTELLIGIBLE], you say, OK,
of course, if you're doing A plus B plus C, what order you
do will make a difference [UNINTELLIGIBLE].
So if you're really careful, you can't do too many things.
But most times, you can do some of these things.
And in fact, in our final thing, we might say--
the final project--
we will give you something that you can change the
algorithm as long as the outcome of the [? image ?]
is the same.
If you look at the [? image, ?] and the
[? image ?] outcome is the same, you
can go change things.
So a lot of times, you might have a lot of freedom to
change the amount of errors you get, the precision, and,
in fact, doing that, that you can actually get a lot of good
performance.
So in here, we are not trying to change the matrix
multiply, of course.
But we are going to change the way order of operations is
being done.
So some people say you can't do that.
It would make matrix multiply different.
But in most cases, it's OK.
So what we have done is done what we call
block matrix multiply.
So instead of calculating rows, you
calculate blocks at a time.
So calculating each block, you only need a
few amount of data.
So this is block matrix multiply.
And voila, by doing block matrix multiply, I got even
1.7x performance gain from that.
Now, it's very interesting.
Now with each block cycle, I am doing two operations.
So my [? superscale ?]
is finally coming to being.
So every clock cycle, I am doing
multiple operations here.
So this is very interesting, the number here.
So now when you look at what happens here, I am actually
executing a little bit more instructions here.
Why?
Because I did block matrix multiply.
That means I am doing a little bit more overhead in here.
I have more loops in here.
My inner loops are small.
So I'm doing more instructions to do execute there.
So I'm doing more instructions, but I have a
huge cache performance gain.
My cache miss rate now going down to very, very little--
0.02%.
And I have an 8x improvement in cache, and that means I got
a 3x improvement in my CPI in here.
That's very nice.
That means even though I'm doing a little bit more work,
my cache behavior really, really helped me in here.
So kind of says why I am getting this performance gain.
So what else can we do?
We can go look at trying to do a lot more crazy optimizations
because if you look at the modern Intel, they have this
thing called [? retro ?] instructions.
We'll get a little bit more detail into that later.
And what we can do sometimes is you can nudge the compiler
to take advantage of these instructions.
Of course, if nothing helps, you can actually go write to
assembly [UNINTELLIGIBLE].
But hey, I don't want you guys to write assembly code, at
least in this class, even though you can get really good
performance sometimes.
But you can kind of give enough hints to compiler and
pray and kind of [UNINTELLIGIBLE]
there's no nice way on these things, and hope for the
compiler to do the right thing.
So in here, we can play with, a lot of
times, compiler flags.
And this is not exact size.
We keep trying those things, and sometimes we can even get
information back and say, OK, what did the compiler do?
It might give you some good hints and say, I couldn't do
something because of this part of the code.
And worst comes to worst, you can actually generate assembly
and stare at it.
And sometimes in this class, you might actually want to
stare at assembly and see what goes on in here.
So here is the assembly for here.
And this is my loop body in here.
And then what I can see, I'm not going to go through that.
Everything is converted into SSE instructions.
At least I know what SSE instructions mean, so this
seems to be doing well, so I'm happy.
And in fact, I'm really happy because I got a 2.8x
improvement by doing this.
Now what I am actually doing each clock cycle about
[? five of my ?]
operations.
So I'm going really fast in here.
So now, incidentally, I'm running my program about
30,000x faster than when I started.
So if you haven't noticed, these things
slowly add up in here.
And now, if we look at what's going on here, it's very
interesting.
What I have done is actually, my each instruction is now CPI
has gone down.
That means each instruction is running slower because I'm
doing these SSE instructions.
And in fact, my cache miss rate has also gone up.
But I am doing a lot less instructions, because SSE
means in one instruction, I am doing a vector.
So I'm actually getting four operations done in one
instruction.
So the number of instructions I execute has
drastically gone down.
So even though my instructions are running a little bit
slower, I can basically amortize that, and I get good
performance in here.
And of course, this one says, OK, now 88% of the
instructions are SSEs.
So I have [UNINTELLIGIBLE]
most of the things to run on vector mode in here.
And of course, you can keep doing more and more and more.
And there are these four things [UNINTELLIGIBLE] matrix
multiply, there are people who spend their entire lifetime
trying to run it faster.
And they have libraries like that, and this thing called a
BLAS library and this thing called Intel Math Kernel.
So they have done a lot more other things, like
pre-fetching, getting everything right.
So here what I do is basically call their library, don't do
anything myself, and this library will do it for me.
And in fact, that library itself gave me another 2.7x
performance gain in there.
Now I'm actually doing 11 instructions every clock cycle
by doing that.
And if I look at it carefully, what they have done is they
are also very into SSE heavy, so they are running almost the
same amount of instructions.
But they managed to actually get, again,
a better miss rate.
They probably figured out some stuff in here, things like
pre-fetching instructions.
And that means they actually brought down the CPI back to
where it was before.
And that's why they gave that performance [INAUDIBLE].
And finally, we are doing a little
bit of parallel execution.
So as you know, multicores are here.
And you're actually going to these 12-core machines, which
is really nice.
Fastest possible machines you can get your
hands on these days.
And it's kind of fun to work with them.
And we can basically get concurrency for parallel
performance.
And a large part of this class at the end is trying to get
good parallel performance.
But there are a lot of issues with parallelism.
I will go a couple of issues now.
Of course, when we get to that part, we will go in detail.
One thing is called Amdahl's Law.
Because if you're trying to run something parallel, you
can't run everything parallel.
When you start something, there's part of the code that
basically starts the computation.
And then there are the parts you can run parallel, and then
other parts you have to also wind down,
measure, things like that.
So what Amdahl's Law says is, even if you have an infinite
number of processors, the maximum speedup you can get is
this-- this the part of the code that are sequential and
[? parts of ?] the code are parallel.
That means if you have only 90% of the code can be
parallelized, the maximum speedup you can get is 10,
even if you have an infinite number of processors.
So 99% of the code is parallel, the maximum speedup
you can get is 100.
So this is very interesting, because even if you have huge
[UNINTELLIGIBLE], if your code has a certain amount of
sequential part, there is a limit of what you can do.
[UNINTELLIGIBLE]
also think of load balancing.
That means when you get parallel, what you are
assuming is all processes are busy all the time.
That's very hard to achieve.
Sometimes some processes are busy.
Sometimes others are not.
Sometimes something's finished early.
So if that happens, even though you have parallels, you
might not get really good performance in here.
And then, of course, there's a thing called granularity of
parallelism.
Normally, what happens in programming, you run
sequential part, you run parallel part, you run
sequential parallel part.
To start parallelism is a big task.
You have to tell all the other processors, OK, look, you guys
have some work.
Go do that, and finish that, and come back.
And that itself is very expensive.
And the work you give is very small.
You give the work.
The start-up and tear-down cost of parallelism itself
might be too much than the speedup you get.
In fact, you can make programs parallel and slow them down
really drastically.
So you have to be very careful in that, because you can say,
oh, I'm running in parallel, but it's running slow.
Why?
Because you might have a granularity issue, because we
are giving so little work that even though you're getting
parallelism, you might not get that much performance.
So if you look at matrix multiply, what happens here is
you can divide the matrix into two different parts in here
and calculate as two different matrix multiplies in a
different core or a different process.
Or in here, I can make it two or whatever in matrix
multiplies to do that.
So this is nicely parallelized.
No big issue in here.
And when I parallelize, I got rather 3.5x performance
improvement.
Of course, I could have gotten more here, but because I had
to start here, since I didn't want to wait for a week for
this to finish, I had to come up with a smaller matrix size.
But now, when I get here, this is so fast, my
granularity is too small.
So that's why I didn't get--
because this was an eight-core machine, I didn't get 8x
because the minute I get parallelism, it just is over
because it's running so fast in here.
Now, at this point, using eight cores, I am actually
getting 36.
So this is not that interesting, because this is
not running on one core, every clock cycle, I am trying to
get 36 operations done.
So the interesting thing to follow in here is that this
example might be somewhat contrived, because I started
with this immutable-type matrix multiply.
Hopefully, you and your colleagues who haven't taken
this class probably won't go that far.
But even if you start somewhere here, still, there's
a huge gain to be made.
So to put this in perspective, OK, so if you look at here,
even if I start here somewhere out in C, I already got 131x
performance improvement.
That's huge.
So that means in this class, we are not
talking about small things.
And in fact, this is why I had to go log scale in doing
absolute grades, because we had people who figured out
exactly how, in some problems, to change data structures,
change execution ordering, do some really cool thing, get
1,000x performance improvement.
And there are other people who did little bit things and got
2x improvement.
So the fact that what you can do the best here can be
really, really high.
So that's why we actually started giving you the second
chance to also go basically see, if you missed a critical
point, to go back and learn that.
So to put this in perspective--
so in summary, what we have done is--
matrix multiply might be an exception.
Every time you go, you might not get that much, but in
here, going from this very software-engineered, nice
[UNINTELLIGIBLE]
to a BLAS [UNINTELLIGIBLE], I got a 296,000 times
performance improvement.
This is huge.
If you put this in perspective, if you look at
miles per gallon between these two-- a
supertanker and this scooter--
it's only 14,000x, basically.
So what that means is you have to have 20 supertankers to
basically match this much improvement.
So if somebody can tell you, look, I can't get a
supertanker to run at the same miles per gallon as your
scooter, that would be revolutionary.
But you can do something similar in your computer.
And in fact, if you look at these large data centers, the
computers are actually burning a lot of
energy, a lot of cost.
And in fact, if you can reduce that, that can have impact.
So what you get out of this class is to think through
these programs this way.
Learn about correctness before [UNINTELLIGIBLE]
6.170, you hopefully are good at writing correct programs.
But that's not good enough in a lot of times.
And how to think about programs, how to make it run
faster, there are multiple [UNINTELLIGIBLE] necessary.
So for example, in here, it's basically direct energy.
So can you get the data center to burn 10% less energy,
that's huge.
For example, if you look at today's supercomputing
centers, they spend about $10 million a year on energy, just
for the power grid.
So if you can say, I'm [? playing with ?]
10%, that's huge.
Other thing, one thing Charles says a lot of times is, what
performance gives you is currency, because performance
itself might not be that directly useful, but it gives
you currency to buy something else.
What it is?
So assume you have a nice application.
Your GUI is doing OK.
It's up to the point that people don't feel stalled.
But if you want to do something addition, if you add
a feature, you don't want to slow down the program too
much, because your users are going to complain.
So what performance gives you is, if you improve the
performance, it gives you currency, the ability to buy
some additional features into your system.
Or you have a system that you created a start up, and you
created this nice server somewhere.
Suddenly, it became very popular, and a million people
want to use your software.
And the software might not be ready to do that.
It might just crash and burn, or you might have to pay a lot
of money for Amazon for the [? cycles, ?]
so by actually making programs faster, you can make it
scalable, you can make it efficient.
So this gives you the ability, this currency, to go do a lot
more other interesting things to your software because now
you can run it faster.
It gives you this margin to play with.
So in this class, we are going to learn a
lot about these things.
We are going to learn about architecture, how looking at
the architectural issues, how to identify when
something is wrong.
We are going to look at algorithmic issues in here,
how to look at things.
We are looking at architectural things like
memory systems, parallelism.
And also we will talk about all the tools, so you will
actually know something has gone wrong.
Because today, you run the program, it runs, it runs
correctly, is it good enough or not?
How do you identify that?
That's, I think, the very interesting place to start.
So that's all I have today.
Make sure you get a Project 0.
And especially people who haven't done C, we will--
Charles, are we going to do a C remediation?
We did last time, didn't we?
OK, guys, one more thing.
Last year, we did an evening C remediation class.
So if you know Java, if you want to know C, we will send
out a time with TAs that will give you kind of a one-hour
crash course into C in there.
And we will send information.
So this class moves very fast.
Get on with Project 0, and see you next week.