0%

安卓开发笔记1

(随手写的,不具有参考价值)
MainActivity.java定义了安卓应用程序入口

res文件夹存放了应用程序运行所需的各种资源,包括动画、图像、布局文件、XML文件、数据资源(如字符串)和原始(raw)文件,下面有layout(各种界面布局的XML文件)、drawble(各种图片)、mipmap(不知道干嘛的)和values(字符串、颜色、样式和数组)文件夹。

页面布局用XML文件来完成,一个典型的XML文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

assert文件夹也是放各种资源,但与res文件夹不同的是,res文件夹的内容会在程序启动时被加载进内存里,而assert文件夹则不会。所以assert文件夹里会放一些比较大的资源文件。

Android程序必须在根目录下包含一个 AndroidManifest.xml文件。 Androidmanifest.xml定义了应用程序的整体布局、提供的内容与动作,还描述了程序的信息,包括应用程序的包名、所包含的组件、服务( Service)、接收器( Receiver)、应用程序兼容的最低版本、图标、应用程序自身应该具有的权限的声明以及其他应用程序访问该应用程序时应该具有的权限等。它是应用程序的重要组成文件。

Android应用程序由Activity、Activity Manager 、 ContentProvider、Service、BroadcastReceiver等部分组成。
Activity用于与用户交互,它有各种Widget组件,如按钮Button、列表List和文本框TextBox。一个程序一般包括多个Activity。

顾名思义,ContentProvider是提供内容的。一个应用程序使用的数据是不能被其他程序访问的。当需要共享数据时,ContentProvider提供了程序之间数据交换的机制。

Service是与 Activity独立且可以保持后台运行的服务,相当于一个在后台运行的没有界面的 Activity。如果应用程序并不需要显示交互界面但却需要长时间运行,就需要启用Service。

BroadcastReceiver用于应用程序间传递信息。

Intent用于连接以上组件,并在它们之间传递信息。

activity

Activity是 Android程序的呈现层,显示可视化的用户界面,并接收与用户交互所产生的界面事件。 Android应用程序可以包含一个或多个 Activity,一般在程序启动后会首先呈现一个主 Activity,用于提示用户程序已经正常启动并显示一个初始的用户界面。
可以把Activity理解为用户界面。
项目创建时,系统会自动创建一个MainActivity,作为程序启动后的第一个界面,根据需要可以从这个Activity启动别的Activity

activity有4种状态:活动状态、暂停状态、停止状态和非活动状态。
活动状态就是能被用户看到,能与用户交互;
暂停状态就是界面上被部分遮挡,不能与用户交互;
停止状态就是被完全遮挡;
非活动状态是上述三者以外的状态。

第四章:用户界面设计基础

Android常用布局包括线性布局、表格布局、相对布局、框架布局和绝对布局。

用户界面通过View和ViewGroup来构建。
View对象是 Android平台上表示用户界面的基本单元,一个View占据屏幕上的一块方形区域,存储了该特定区域的布局参数和内容。 Android平台下View类是所有可视化控件的基类,主要提供控件绘制和事件处理的方法。

ViewGroup是由View和其他ViewGroup组成的。
各种布局类的关系如下图:
image.png

常用的布局属性

ID属性

一个控件会有一个ID,Java代码中可以根据这个ID来找到相应的控件。
在XML中一般以字符串的形式定义一个ID。如android:id=”@+id/tvHello”。
在Java中可以通过调用 Activity.findviewByld()方法引用相应的ID,并创建这个View对象的实例。

尺寸参数

尺寸参数包括layout_width和layout_length,给出了这个View的大小。它们的值可以是一个具体的数字,如50px;也可以是fill_parent,使它填充尽可能多的空间;还可以是wrap_content,使其恰好能显示其内部的内容

xmlns:android属性

一般取值为”http://schemas.android.com/apk/res/android"。

各种常用布局方式

线性布局

线性布局的子控件定义在<Linearlayout>和</Linearlayout>标签之间。线性布局是最简单的布局之一,它将其包含的 Widget控件元素按水平或者垂直方向顺序排列。
布局方向由属性 orientation的值来决定。同时,使用此布局时可以通过设置控件的 weight参数控制各个控件在容器中的相对大小。
在线性布局中可使用 gravity属性来设置控件的对齐方式。gravity属性的可供取值有top(到容器顶)、bottom(底)、left(左)、 right(右)、 center_vertical(纵向中央)、 center_horizonal(横向中央)、 center(中央)、 fill vertical(纵向拉伸以填满容器)、 fill horizonal(横向拉伸以填满容器)、fill(纵向横向同时拉伸以填满容器)等。当需要为 gravity设置多个属性值时,要用“|”每个属性值隔开。

表格布局

表格布局以<TableLayout>开始,用<TableRow>代表一个行,每个行可以有很多列。如果一个自控件没有在任何<TableRow>中,会单独成一行。
TableLayout中可以设置三种属性:
1.Shrinkable:如果一个列被设置为shrinkable,则该列的宽度可以收缩,以适应其父容器的大小。
2.Stretchable:如果一个列被设置为stretchable,则该列的宽度可以伸长,以填满表格中剩余的空间。
3.Collapsed:如果一个列被设置为collapsed,则该列会被隐藏。

相对布局

相对布局以<RelativeLayout>开始。顾名思义,相对布局就是让自控件的位置有相互依赖关系。
相对布局中, android: layout_centerlnparent属性指定是否位于父控件的中央位置, android:layout_below属性指定位置为在参照控件的下方, android: layout_toRightOf属性指定位置为在参照控件右侧, android: layout_alignBottom属性指定与参照控件底部对齐。还可以用android:marginLeft等具体调整位置。

绝对布局

绝对布局,就是让程序员自己直接指定控件的位置。绝对布局以<AbsoluteLayout>开始。自控件可以通过layout_x和layout_y指定位置。

框架布局

框架布局以<FrameLayout>开始,可以让不同的自控件叠在一起。

Widget控件

Widget控件好像就是各种常用的自控件。常用的Widget控件有TextView、EditView、Button、CheckBox和RadioButton等。

TextView和EditText

TextView用于在屏幕上显示文字信息。EditText用于接受用户从键盘输入的文本信息,是TextView的子类。
TextView可以用textColor来指定颜色,textStyle来指定字体风格,textSize来指定大小,background来指定背景颜色,gravity来指定位置。
EditText可以用hint来给输入的文本框增加提示信息,password来设置是否将输入以密码显示。

Button

可以用style来指定按钮的风格,也可以自己定义其他的风格。

CheckBox

CheckBox是一种可以支持多个选项的控件。一般可以在一个Layout下定义多个CheckBox以供选择。可以设置checked=”true”来将这个选项默认勾选。

RadioGroup和RadioButton

RadioGroup和RadioButton用于提供单选按钮。一个RadioGroup下面可以有很多个RadioButton作为选项。RadioButton里同样可以设置checked=”true”来将这个选项默认勾选。

安卓中的事件处理机制

在图形用户界面的开发设计中,有两个非常重要的内容:一个是控件的布局,另一个是控件的事件处理。 Android在事件处理过程中主要涉及3个概念。
(1)事件:表示用户在图形界面的操作的描述,通常是封装成各种类,例如,键盘事件操作相关的类为 Keyevent,触摸屏相关的移动事件类为 Motionevent等。
(2)事件源:事件源是指发生事件的控件,例如, Button、 Edittext等。
(3)事件处理者:事件处理者是指接收事件并对其进行处理的对象,一般是一个实现某些特定接口类的对象
事件的处理方法有两类,即”基于监听接口”和”基于回掉机制”。

基于监听接口的事件处理机制

有三种实现方式:
直接实现接口的处理方式、内部类处理方式和匿名内部类处理方式。

直接实现接口的处理方式

在定义activity时实现接口,这样activity本身就是事件监听器。
处理按钮点击事件时,一般需要调用该按钮实例的setOnClickListener()方法,并把View.OnClickListener对象的实例作为参数传入。一般是在View.OnClickListener的OnClick()方法里处理按钮的单击事件。
代码示例如下:(没有import和package)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button MyButton=(Button)findViewById(R.id.btn_normal);
MyButton.setOnClickListener(this);
}
public void onClick(View v){
switch (v.getId()){
case R.id.btn_normal:
TextView mytext=(TextView)findViewById(R.id.tv_button);
mytext.setText("普通按钮被点击");
}
}
}
内部类处理方式

这种方法需要在activity里再定义一个类,用这个类实现OnClickListener接口,然后让控件绑定到这个类的一个实例上。代码示例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MainActivity extends AppCompatActivity{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button MyButton=(Button)findViewById(R.id.btn_normal);
MyButton.setOnClickListener(new MyClickListener());
}
class MyClickListener implements View.OnClickListener{
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_normal:
TextView mytext=(TextView)findViewById(R.id.btn_normal);
mytext.setText("普通按钮被点击");
break;
}
}
}
}
匿名内部类处理方式

使用匿名内部类处理方式是一种最常用的方式,因为大部分事件监听器只会被使用一次,所以这种方式更为合适。
代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends AppCompatActivity{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button MyButton=(Button)findViewById(R.id.btn_normal);
MyButton.setOnClickListener(new View.OnClickListener(){
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_normal:
TextView mytext=(TextView)findViewById(R.id.btn_normal);
mytext.setText("普通按钮被点击");
break;
}
}
});
}
}

基于回调机制的事件处理

回调机制指的是事件被系统捕获后发送到View,View会调用相应的回调方法(如果有的话)进行处理。当某个事件没有被任何View处理时,便会调用activity中的回调方法。

onKeyDown()方法

该方法用于捕获设备键盘被按下的事件,定义如下:

1
public boolean onKeyDown(int keyCode,KeyEvent event)

keyCode是按下的键盘码,event是按键事件对应的对象,包括了各种信息。
返回一个boolean值,如果为true代表了事件已经被完整地处理了,并不希望其他回调方法进行处理,如果为false则相反。

类似的有onKeyUp()方法用来捕获设备键盘被抬起的事件。

onTouchEvent()方法

onTouchEvent()方法用于捕获触摸屏事件并进行处理。定义如下:

1
public boolean onTouchEvent(MotionEvent event)

参数event包含了事件的各种信息,如触摸的位置、类型和时间等。返回值的意义与onKeyDown()方法类似。一个示例程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainActivity extends AppCompatActivity{
TextView TxtAction,TxtPosition;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TxtAction=(TextView)findViewById(R.id.tv_action);
TxtPosition=(TextView)findViewById(R.id.tv_position);
}
public boolean onTouchEvent(MotionEvent event){
int Action=event.getAction();
float x=event.getX();
float y=event.getY();
TxtAction.setText("触屏动作:"+Action);
TxtPosition.setText("当前坐标"+"("+x+","+y+")");
return true;
}
}

这个程序会捕获触屏位置并显示这个位置的坐标。

直接绑定到标签的事件处理方法

这种方法可以在xml文档里给View设置一个事件处理方法,具体做法是个给View加一个类似于onClick等属性,属性值是一个方法名。然后在activity类里定义这个方法。
一个示例程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_button"
android:text=""
android:hint="请输入:">
</EditText>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn_normal"
android:text="普通按钮">
</Button>
<Button
style="?android:buttonStyleToggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/btn_small"
android:text="小按钮"
android:onClick="buttonClick">
</Button>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_action"
>
</TextView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tv_position">
</TextView>
</LinearLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MainActivity extends AppCompatActivity{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void buttonClick(View v){
switch (v.getId()){
case R.id.btn_small:
TextView mytext=(TextView)findViewById(R.id.btn_small);
mytext.setText("你好,小按钮被点击!");
break;
}
}
}

小练习:加法计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="horizontal">
<EditText
android:layout_width="100dp"
android:layout_height="wrap_content"
android:id="@+id/et_1"
android:text=""
android:hint="请输入数字">
</EditText>
<TextView
android:layout_width="50dp"
android:layout_height="wrap_content"
android:id="@+id/tv_message"
android:textSize="40dp"
android:gravity="center"
android:text="+">
</TextView>
<EditText
android:layout_width="100dp"
android:layout_height="wrap_content"
android:id="@+id/et_2"
android:text=""
android:hint="请输入数字">
</EditText>
<Button
android:layout_width="50dp"
android:layout_height="wrap_content"
android:id="@+id/bt_1"
android:textSize="30dp"
android:text="=">
</Button>
<TextView
android:layout_width="50dp"
android:layout_height="wrap_content"
android:text=""
android:id="@+id/tv_1">
</TextView>
</LinearLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
TextView tv_1;
EditText et_1,et_2;
Button bt_1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_1=findViewById(R.id.tv_1);
et_1=findViewById(R.id.et_1);
et_2=findViewById(R.id.et_2);
bt_1=findViewById(R.id.bt_1);
bt_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Integer in1=new Integer(et_1.getText().toString());
Integer in2=new Integer(et_2.getText().toString());
Integer out1=in1+in2;
tv_1.setText(""+out1);
}
});
}
}

一点小总结

设计用户界面,首先要在xml文档里定义各种控件,然后在activity类里将它们实例化,然后用各种监听器来与用户交互。值得注意的是RadioGroup.OnCheckedChangeListener与CheckBox.OnCheckedChangeListener不能一起用,要用CompoundButton.OnCheckedChangeListener。
`

安卓开发笔记2

(随手写的,不具有参考价值)

Toast

Toast 是一个View视图,用于显示临时的信息,不会与用户进行交互。
它可以显示一个字符串,也可以显示一个View对象。
显示字符串时,可以在activity里调用

1
Toast.makeText(getApplicationContext(),"要显示的字符串",Toast.LENGTH_SHORT).show();

第三个参数是Toast显示的时长,可以换成Toast.LENGTH_LONG,或者用数字直接指定时长,如2000指定2000ms。

显示View时,可以调用setView(view)方法,还可以用setGravity()方法来定位。

Notification

学习这部分内容的时候遇到了各种版本不兼容的问题,暂时跳过,以后在补。

AutoCompleteTextView

AutoCompleteTextView用于输入时的自动补全。它在xml文件中的添加方式与TextView差不多。在activity中需要为它创建一个ArrayAdapter适配器,再调用setAdapter()函数。下面是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
public class MainActivity extends AppCompatActivity {
AutoCompleteTextView atv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
atv=findViewById(R.id.actv_1);
String [] strs={"abc","abcd","geiwj","wbgoiejoje","略略略略略略"};
ArrayAdapter<String>adapter=new ArrayAdapter<String>(this,R.layout.support_simple_spinner_dropdown_item,strs);
atv.setAdapter(adapter);
}
}

值得注意的是在app中需要输入至少两个字母或者汉字,AutoCompleteTextView才会进行提示。
还可以在res/values中添加一个arrays.xml文件,在里面添加一些字符串内容,就不用在activity文件中定义一个String数组了。

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<string-array name="autoStrings">
<item>这这这</item>
<item>那那那</item>
<item>服服服</item>
<item>事实上</item>
<item>啊啊啊</item>
<item>wegnewkngeknflek</item>
<item>我我我</item>
</string-array>
</resources>

然后就可以在将adapter的定义改为

1
ArrayAdapter<String>adapter=new ArrayAdapter<String>(this,R.layout.support_simple_spinner_dropdown_item,getResources().getStringArray(R.array.autoStrings));

对话框

对话框用于弹出一个子界面,并代替原有的界面来与用户交互。常用的对话框有提示对话框AlterDialog、进度对话框ProcessDialog、日期选择对话框DatePickerDialog和时间选择对话框TimePickerDialog等。

提示对话框AlterDialog

AlterDialog需要用AlterDialog.Builder来创建。一般我们定义一个AlterDialog.Builder变量,然后调用它的各种set()方法,最后调用它的show()方法显示对话框。
常用的方法有setTitle()显示标题,setMessage()方法显示内容,setNegativeButton()、setPositiveButton()和setNeutralButton()方法显示一个按钮。
一个示例程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainActivity extends AppCompatActivity {
AutoCompleteTextView atv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnstart=findViewById(R.id.bt_1);
btnstart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder myDialog=new AlertDialog.Builder(MainActivity.this);
myDialog.setTitle("提示");
myDialog.setMessage("这是一个AlertDialog对话框");
myDialog.setPositiveButton("是",null);
myDialog.setNegativeButton("否",null);
myDialog.setNeutralButton("取消",null);
myDialog.show();
}
});
}
}

进度条对话框ProgressDialog

ProgressDialog的用法与AlertDialog差不多。
用setProgressStyle()方法可以设置ProgressDialog的样式,ProgressDialog.STYLE_HORIZONTAL表示水平进度条,ProgressDialog.STYLE_SPINNER表示圆形进度条。如果希望ProgressDialog不能被取消,可以调用ProgressDialog.Cancelable(false)。
一个示例程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MainActivity extends AppCompatActivity {
private Button bt_1,bt_2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt_1=findViewById(R.id.bt_1);
bt_2=findViewById(R.id.bt_2);
bt_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ProgressDialog progressDialog=new ProgressDialog(MainActivity.this);
progressDialog.setTitle("提示信息");
progressDialog.setMessage("正在下载中,请稍候");
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.show();
}
});
bt_2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ProgressDialog progressDialog=new ProgressDialog(MainActivity.this);
progressDialog.setTitle("提示信息");
progressDialog.setMessage("正在下载中,请稍候");
progressDialog.setMax(100);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.show();
}
});
}
}

列表控件ListView

ListView以列表的形式展示内容,当用户点击内容是可以进行交互。与AutoCompleteTextView一样,ListView也需要配置适配器,一般使用ArrayAdapter。ListView的列表项Item被单击会触发OnItemClick事件,被选择会触发OnItemSelect事件,我们可以定义响应两种事件的方法。
我们可以自定义Item的布局,也可以使用Android系统提供的R.layout.simple_list_item_1。
自定义时需要在layout里创建一个xml文件,示例代码如下:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:textSize="18sp">
</TextView>

ListView的常用属性还有divider和dividerHeight,前者可以设置相邻两个列表项的分界线样式,后者可以设置分界线高度。

在activity_main.xml中可以这样添加ListView控件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="vertical">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/tv_message"
android:text="ListView示例\n">
</TextView>
<ListView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/listview"
android:background="#cccccc">
</ListView>
</LinearLayout>

然后可以编写MainActivity.java的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView myListView=findViewById(R.id.listview);
ArrayAdapter<String>adapter=new ArrayAdapter<String>(this,R.layout.list_item,getResources().getStringArray(R.array.autoStrings));
myListView.setAdapter(adapter);
myListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String ItemString=((TextView)view).getText().toString();
Toast.makeText(MainActivity.this,"您单击了列表项:"+ItemString,Toast.LENGTH_SHORT).show();
}
});
myListView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String ItemString=((TextView)view).getText().toString();
Toast.makeText(MainActivity.this,"您选择了列表项:"+ItemString,Toast.LENGTH_SHORT).show();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {

}
});
}
}

下拉列表Spinner

它与ListView基本一样,不再赘述。

DatePicker

DatePicker用于让用户选择当前日期,日期范围是1900年1月1日到2100年12月31日。改变日期会触发onDateChanged事件,所以要实现DatePicker.OnDateChangedListener中的onDateChanged()方法。
在xml布局文件中定义DatePicker的方法跟其他控件基本一样,不再赘述。
示例程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MainActivity extends Activity {
private DatePicker myDatePicker;
private TextView textDate;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.setTitle("日期控件的示例");
textDate=findViewById(R.id.tv_1);
myDatePicker=findViewById(R.id.dp_1);
Calendar calendar=Calendar.getInstance(Locale.CHINA);
int year=calendar.get(Calendar.YEAR);
int month=calendar.get(Calendar.MONTH);
int day=calendar.get(Calendar.DAY_OF_MONTH);
myDatePicker.init(year,month,day,new DatePicker.OnDateChangedListener(){
public void onDateChanged(DatePicker view,int y,int m,int d){
textDate.setText("\n您选择的日期是:"+y+"年"+(m+1)+"月"+d+"日");
}
});
}
}

TimePicker

与DatePicker基本一样,不再赘述。

DatePickerDialog和TimePickerDialog

这两个是弹一个对话框出来,跟AlertDialog差不多。
参考程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.setTitle("日期和时间选择对话框");

Button bt_1=findViewById(R.id.bt_1),bt_2=findViewById(R.id.bt_2);
bt_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
DatePickerDialog datePickerDialog=new DatePickerDialog(MainActivity.this);
datePickerDialog.setOnDateSetListener(new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
Toast.makeText(getApplicationContext(),"日期:"+year+"-"+(month+1)+"-"+dayOfMonth,Toast.LENGTH_SHORT).show();
}
});
datePickerDialog.show();
}
});
bt_2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TimePickerDialog timePickerDialog=new TimePickerDialog(MainActivity.this, new TimePickerDialog.OnTimeSetListener() {
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
Toast.makeText(getApplicationContext(),"Time: "+hourOfDay+":"+minute,Toast.LENGTH_SHORT).show();
}
},14,55,true);
timePickerDialog.show();
}
});
}
}

AnalogClock和DigitalClock

Analog用于显示模拟时钟,DigitalClock用于显示数字时钟。只需要在xml文档里声明一下即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_height="fill_parent"
android:layout_width="fill_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="数字时钟:">
</TextView>
<DigitalClock
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</DigitalClock>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="模拟时钟:">
</TextView>
<AnalogClock
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</AnalogClock>
</LinearLayout>

安卓开发练习

(学的很菜,不具有参考价值)

用户登录对话框

设计一个按钮,按一下弹出对话框,在对话框里用户可以输入用户名和密码,按确定后可以显示用户名和密码。

new_layout.xml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android='http://schemas.android.com/apk/res/android'
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:id="@+id/ll_1">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/et_1"
android:text=""
android:hint="请输入你的用户名">
</EditText>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/et_2"
android:text=""
android:hint="请输入你的密码"
android:inputType="textPassword">
</EditText>
</LinearLayout>

MainActivity文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.example.myapplication;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {
String yhm,mm;
EditText et_1,et_2;
TextView tv_1;
LinearLayout ll_1;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnstart=findViewById(R.id.bt_1);
ll_1=findViewById(R.id.ll_1);
tv_1=findViewById(R.id.tv_1);
String a=tv_1.getText().toString();
final LayoutInflater factory = LayoutInflater.from(MainActivity.this);
final View view = factory.inflate(R.layout.new_layout, null);
btnstart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder myDialog=new AlertDialog.Builder(MainActivity.this);
myDialog.setTitle("提示");
myDialog.setMessage("这是一个AlertDialog对话框");
myDialog.setView(view);
myDialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
et_1=view.findViewById(R.id.et_1);
yhm=et_1.getText().toString();
et_2=view.findViewById(R.id.et_2);
mm=et_2.getText().toString();
Toast.makeText(MainActivity.this,"用户名是:"+yhm+"\n密码是:"+mm,Toast.LENGTH_SHORT).show();
}
});
myDialog.setNegativeButton("取消",null);
myDialog.show();
}
});
}
}

省市列表

第一个列表选择省份,第二个列表选择具体城市。
xml文档如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<Spinner
android:layout_width="50pt"
android:layout_height="wrap_content"
android:id="@+id/sp_1">
</Spinner>
<Spinner
android:layout_width="50pt"
android:layout_height="wrap_content"
android:id="@+id/sp_2">
</Spinner>
</LinearLayout>

activity如下:(本来应该用setOnItemClickedListener的,但用了会闪退,并不知道为什么)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class MainActivity extends Activity {
private String myProvince,myCity;
private Spinner sp_1,sp_2;
private String []provinces=new String[]{"辽宁","吉林","黑龙江","河北","山西","陕西","甘肃","青海","山东","安徽","江苏","浙江","河南","湖北","湖南","江西","台湾","福建","云南","海南","四川","贵州","广东","内蒙古","新疆","广西","西藏","宁夏","北京","上海","天津","重庆","香港","澳门"};
private String [][]cities=new String[][]{{"沈阳","大连","鞍山","抚顺","本溪","丹东","锦州","营口","阜新","辽阳","盘锦","铁岭","朝阳","葫芦岛"}
,{"长春","吉林","四平","辽源","通化","白山","松原","白城","延边"},{"哈尔滨","齐齐哈尔","鸡西","鹤岗","双鸭山","大庆","伊春","佳木斯","七台河","牡丹江","黑河","绥化","大兴安岭"}
,{"石家庄","唐山","秦皇岛","邯郸","邢台","保定","张家口","承德","沧州","廊坊","衡水"}
,{"太原","大同","阳泉","长治","晋城","朔州","晋中","运城","忻州","临汾","吕梁"}
,{"西安","铜川","宝鸡","咸阳","渭南","延安","汉中","榆林","安康","商洛"}
,{"兰州","嘉峪关","金昌","白银","天水","武威","张掖","平凉","酒泉","庆阳","定西","陇南","临夏","甘南"}
,{"西宁","海东","海北","黄南","海南","果洛","玉树","海西"}
,{"济南","青岛","淄博","枣庄","东营","烟台","潍坊","威海","济宁","泰安","日照","莱芜","临沂","德州","聊城","滨州","菏泽"}
,{"合肥","芜湖","蚌埠","淮南","马鞍山","淮北","铜陵","安庆","黄山","滁州","阜阳","宿州","巢湖","六安","亳州","池州","宣城"}
,{"南京","无锡","徐州","常州","苏州","南通","连云港","淮安","盐城","扬州","镇江","泰州","宿迁"}
,{"杭州","宁波","温州","嘉兴","湖州","绍兴","金华","衢州","舟山","台州","丽水"}
,{"郑州","开封","洛阳","平顶山","焦作","鹤壁","新乡","安阳","濮阳","许昌","漯河","三门峡","南阳","商丘","信阳","周口","驻马店"}
,{"武汉","黄石","襄樊","十堰","荆州","宜昌","荆门","鄂州","孝感","黄冈","咸宁","随州","恩施"}
,{"长沙","株洲","湘潭","衡阳","邵阳","岳阳","常德","张家界","益阳","郴州","永州","怀化","娄底","湘西"}
,{"南昌","景德镇","萍乡","九江","新余","鹰潭","赣州","吉安","宜春","抚州","上饶"}
,{"台北","高雄","基隆","台中","台南","新竹","嘉义"}
,{"福州","厦门","莆田","三明","泉州","漳州","南平","龙岩","宁德"}
,{"昆明","曲靖","玉溪","保山","昭通","丽江","普洱","临沧","文山","红河","西双版纳","楚雄","大理","德宏","怒江","迪庆"}
,{"海口","三亚"}
,{"成都","自贡","攀枝花","泸州","德阳","绵阳","广元","遂宁","内江","乐山","南充","宜宾","广安","达州","眉山","雅安","巴中","资阳","阿坝","凉山"}
,{"贵阳","六盘水","遵义","安顺","铜仁","毕节","黔西南","黔东南","黔南"}
,{"广州","深圳","珠海","汕头","韶关","佛山","江门","湛江","茂名","肇庆","惠州","梅州","汕尾","河源","阳江","清远","东莞","中山","潮州","揭阳","云浮"}
,{"呼和浩特","包头","乌海","赤峰","通辽","鄂尔多斯","呼伦贝尔","巴彦淖尔","乌兰察布","兴安","锡林郭勒","阿拉善"}
,{"乌鲁木齐","克拉玛依","吐鲁番","哈密","和田","阿克苏","喀什","克孜勒苏柯尔克孜","巴音郭楞蒙古","昌吉","博尔塔拉蒙古","伊犁哈萨克","塔城","阿勒泰"}
,{"南宁","柳州","桂林","梧州","北海","防城港","钦州","贵港","玉林","百色","贺州","河池","来宾","崇左"}
,{"拉萨","昌都","山南","那曲","阿里","林芝"}
,{"银川","石嘴山","吴忠","固原","中卫"}
,{},{},{},{},{},{}};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sp_1=findViewById(R.id.sp_1);
sp_2=findViewById(R.id.sp_2);
ArrayAdapter<String>adapter=new ArrayAdapter<String>(this,R.layout.support_simple_spinner_dropdown_item,provinces);
sp_1.setAdapter(adapter);
sp_1.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
ArrayAdapter<String>adapterr=new ArrayAdapter<String>(MainActivity.this,R.layout.list_item,cities[position]);
sp_2.setAdapter(adapterr);
myProvince=((TextView)view).getText().toString();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {

}
});
sp_2.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
myCity=((TextView)view).getText().toString();
Toast.makeText(MainActivity.this,"你选择了: "+myProvince+"省"+myCity+"市",Toast.LENGTH_SHORT).show();
}

@Override
public void onNothingSelected(AdapterView<?> parent) {

}
});
}
};

动态下拉列表

用一个EditText来跟用户交互。用户按下”添加”按钮就把EditText的内容添加到下拉列表里,按下”删除”按钮就降下拉列表里EditText的内容删除(如果有的话)。
xml文档如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:orientation="vertical">
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/et_1"
android:hint="请输入…">
</EditText>
<Spinner
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/sp_1">
</Spinner>

<Button
android:id="@+id/bt_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加"></Button>

<Button
android:id="@+id/bt_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="删除"></Button>

</LinearLayout>

activity_main文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class MainActivity extends Activity {
private Button bt_1,bt_2;
private EditText et_1;
private Spinner sp_1;
private ArrayList<String> strings;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_1=findViewById(R.id.et_1);
bt_1=findViewById(R.id.bt_1);
bt_2=findViewById(R.id.bt_2);
sp_1=findViewById(R.id.sp_1);
strings=new ArrayList<String>();
bt_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String tem=et_1.getText().toString();
strings.add(tem);
ArrayAdapter<String>adapter=new ArrayAdapter<String>(MainActivity.this,R.layout.support_simple_spinner_dropdown_item,strings);
sp_1.setAdapter(adapter);
Toast.makeText(MainActivity.this,"添加成功!",Toast.LENGTH_SHORT).show();
}
});
bt_2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String tem=et_1.getText().toString();
if(strings.contains(tem)){
strings.remove(tem);
ArrayAdapter<String>adapter=new ArrayAdapter<String>(MainActivity.this,R.layout.support_simple_spinner_dropdown_item,strings);
sp_1.setAdapter(adapter);
Toast.makeText(MainActivity.this,"删除成功!",Toast.LENGTH_SHORT).show();
}
else{
Toast.makeText(MainActivity.this,"删除失败!",Toast.LENGTH_SHORT).show();
}
}
});
}
};

并行与分布式计算作业2

姓名 学号 班级 邮箱
张景润 18340210 计科八班 zhangjr35@mail2.sysu.edu.cn

第一题

题目

  分别采用不同的算法(非分布式算法)例如一般算法、分治算法和Strassen算法等计算计 算矩阵两个300x300的矩阵乘积,并通过Perf工具分别观察cache miss、CPI、 mem_load 等性能指标。

解答

一般算法

  编写一个矩阵乘法程序,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<bits/stdc++.h>
#define SIZE_OF_MATRIX 300
using namespace std;
struct matrix{
int n,m;
vector<vector<double> > vec;
matrix(int nn=0,int mm=0):n(nn),m(mm){
vec=vector<vector<double> > (n);
for(int i=0;i<n;i++)vec[i]=vector<double> (m);
}
void get_rand(){
for(int i=0;i<n;i++)for(int j=0;j<m;j++)vec[i][j]=(double)rand()/(INT_MAX);
}
void output(){
for(int i=0;i<n;i++){
for(int j=0;j<m;j++)cout<<vec[i][j]<<" ";
cout<<"\n";
}
}
matrix operator *(matrix b){
if(m!=b.n){
return matrix();
}
matrix ret(n,b.m);
for(int i=0;i<ret.n;i++){
for(int j=0;j<ret.m;j++){
for(int k=0;k<m;k++){
ret.vec[i][j]+=vec[i][k]*b.vec[k][j];
}
}
}
return ret;
}
};
int main(void){
//cout<<"Hello world\n";
matrix a(SIZE_OF_MATRIX,SIZE_OF_MATRIX),b(SIZE_OF_MATRIX,SIZE_OF_MATRIX);
a.get_rand(),b.get_rand();
//a.output(),b.output();

matrix c=a*b;
//c.output();
}

  使用perf观察 cache-misses,cpi,mem-loads 等性能指标:

1
perf stat -e cache-misses,instructions,cycles,mem-loads -r 10  ./a

结果如下:
image.png
  可以看到,10次运行的平均cache-misses次数只有38100次,相比于3e9级别的指令数量,这个数字还是比较少的。
  平均指令数为3,373,445,889,平均cpu cycles只有 1,189,821,710,计算得$$CPI=\frac{1,189,821,710}{3,373,445,889}\approx0.3527$$这是一个小于1的数字,十分奇怪,我推测是编译器或者是CPU在底层对循环进行了并行优化。
  而mem_loads为0,这也十分奇怪,大概是perf出了什么bug。
  用perf record 对cache-misses 和 instructions的分布进行详细分析:

1
perf record -e cache-misses,instructions -g ./a

结果如下:
N7C_FJC~_W7KBF080VFUQHO.png

![57UDL_OJL_`3HPY1F0ND3ZS.png](https://i.loli.net/2020/03/30/Hhj6YGoLQwitACD.png)
  可以看到,矩阵乘法阶段占用instructions的比例较高,发生的cache-misses次数也较多。

分治算法

  这里的分治算法是利用矩阵分块将矩阵分为4个子矩阵,这样就可以对两个二乘二的矩阵进行乘法,最后再合并起来得到结果。简单的分治算法的时间复杂度仍然是$O(n^3)$的。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include<bits/stdc++.h>
#define SIZE_OF_MATRIX 300
using namespace std;
const double eps=1e-6;
struct matrix{
int n,m;
vector<vector<double> > vec;
matrix(int nn=0,int mm=0):n(nn),m(mm){
vec=vector<vector<double> > (n);
for(int i=0;i<n;i++)vec[i]=vector<double> (m);
}
void get_rand(){
for(int i=0;i<n;i++)for(int j=0;j<m;j++)vec[i][j]=(double)rand()/(INT_MAX);
}
void output(){
for(int i=0;i<n;i++){
for(int j=0;j<m;j++)cout<<vec[i][j]<<" ";
cout<<"\n";
}
}
matrix operator *(matrix &b){
if(m!=b.n){
return matrix();
}
matrix ret(n,b.m);
for(int i=0;i<ret.n;i++){
for(int j=0;j<ret.m;j++){
for(int k=0;k<m;k++){
ret.vec[i][j]+=vec[i][k]*b.vec[k][j];
}
}
}
return ret;
}

matrix operator+(const matrix& b){
if(n!=b.n&&m!=b.m){
return matrix();
}
matrix ret(n,m);
for(int i=0;i<ret.n;i++){
for(int j=0;j<ret.m;j++){
ret.vec[i][j]=vec[i][j]+b.vec[i][j];
}
}
return ret;
}

vector<matrix> divide(){
matrix m1(n/2,m/2),m2(n/2,m-m/2),m3(n-n/2,m/2),m4(n-n/2,m-m/2);
for(int i=0;i<m1.n;i++){
for(int j=0;j<m1.m;j++){
m1.vec[i][j]=vec[i][j];
}
}
for(int i=0;i<m2.n;i++){
for(int j=0;j<m2.m;j++){
m2.vec[i][j]=vec[i][j+m1.m];
}
}
for(int i=0;i<m3.n;i++){
for(int j=0;j<m3.m;j++){
m3.vec[i][j]=vec[i+m1.n][j];
}
}
for(int i=0;i<m4.n;i++){
for(int j=0;j<m4.m;j++){
m4.vec[i][j]=vec[i+m1.n][j+m1.m];
}
}
return vector<matrix>{m1,m2,m3,m4};
}


};

matrix merge(vector<matrix> &matrices){
matrix ret(matrices[0].n+matrices[3].n,matrices[0].m+matrices[3].m);
for(int i=0;i<matrices[0].n;i++){
for(int j=0;j<matrices[0].m;j++){
ret.vec[i][j]=matrices[0].vec[i][j];
}
}
for(int i=0;i<matrices[1].n;i++){
for(int j=0;j<matrices[1].m;j++){
ret.vec[i][j+matrices[0].m]=matrices[1].vec[i][j];
}
}
for(int i=0;i<matrices[2].n;i++){
for(int j=0;j<matrices[2].m;j++){
ret.vec[i+matrices[0].n][j]=matrices[2].vec[i][j];
}
}
for(int i=0;i<matrices[3].n;i++){
for(int j=0;j<matrices[3].m;j++){
ret.vec[i+matrices[0].n][j+matrices[0].m]=matrices[3].vec[i][j];
}
}
return ret;
}

matrix divide_conquer(matrix &a,matrix &b){
if(a.n<10)return a*b;
vector<matrix> vec1=a.divide(),vec2=b.divide();
// cout<<vec1[0].n<<" "<<vec2[3].m<<endl;
vector<matrix> sub_matrix(4);
sub_matrix[0]=divide_conquer(vec1[0],vec2[0])+divide_conquer(vec1[1],vec2[2]);
sub_matrix[1]=divide_conquer(vec1[0],vec2[1])+divide_conquer(vec1[1],vec2[3]);
sub_matrix[2]=divide_conquer(vec1[2],vec2[0])+divide_conquer(vec1[3],vec2[2]);
sub_matrix[3]=divide_conquer(vec1[2],vec2[1])+divide_conquer(vec1[3],vec2[3]);
return merge(sub_matrix);
}
int main(void){
//cout<<"Hello world\n";
matrix a(SIZE_OF_MATRIX,SIZE_OF_MATRIX),b(SIZE_OF_MATRIX,SIZE_OF_MATRIX);
a.get_rand(),b.get_rand();
//a.output(),b.output();
matrix c=divide_conquer(a,b);
//matrix d=a*b;
bool ok=true;
/*for(int i=0;i<SIZE_OF_MATRIX;i++){
for(int j=0;j<SIZE_OF_MATRIX;j++)if(abs(c.vec[i][j]-d.vec[i][j])>eps)ok=false;//cout<<i<<" "<<j<<" "<<c.vec[i][j]<<" "<<d.vec[i][j]<<endl;
}
cout<<ok<<endl;*/
// c.output();
}

  同样是使用

1
perf stat -e cache-misses,instructions,cycles,mem-loads -r 10  ./a

  来对性能进行分析,结果如下图所示:
image.png

  可以看到cache-misses次数与之前基本一样;CPI升高到了0.5208,仍然小于1,说明底层的并行化效果不错;而mem-loads仍然为0。
  平均时间消耗达到了将近3秒,是之前的4倍还要多,说明程序在递归调用时消耗了大量时间。

strassen算法

  strassen算法用很神奇的方法把矩阵乘法的时间复杂度降低到了$O(n^{2.81})$左右,在矩阵规模较大的情况下性能会更好。
  代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
#include<bits/stdc++.h>
#define SIZE_OF_MATRIX 300
using namespace std;
const double eps=1e-6;
struct matrix{
int n,m;
vector<vector<double> > vec;
matrix(int nn=0,int mm=0):n(nn),m(mm){
vec=vector<vector<double> > (n);
for(int i=0;i<n;i++)vec[i]=vector<double> (m);
}
void get_rand(){
for(int i=0;i<n;i++)for(int j=0;j<m;j++)vec[i][j]=(double)rand()/(INT_MAX);
}
void output(){
for(int i=0;i<n;i++){
for(int j=0;j<m;j++)cout<<vec[i][j]<<" ";
cout<<"\n";
}
}
matrix operator *(const matrix &b)const{
if(m!=b.n){
return matrix();
}
matrix ret(n,b.m);
for(int i=0;i<ret.n;i++){
for(int j=0;j<ret.m;j++){
for(int k=0;k<m;k++){
ret.vec[i][j]+=vec[i][k]*b.vec[k][j];
}
}
}
return ret;
}

matrix operator+(const matrix& b)const{
if(n!=b.n&&m!=b.m){
return matrix();
}
matrix ret(n,m);
for(int i=0;i<ret.n;i++){
for(int j=0;j<ret.m;j++){
ret.vec[i][j]=vec[i][j]+b.vec[i][j];
}
}
return ret;
}

matrix operator-(const matrix& b)const{
if(n!=b.n&&m!=b.m){
return matrix();
}
matrix ret(n,m);
for(int i=0;i<ret.n;i++){
for(int j=0;j<ret.m;j++){
ret.vec[i][j]=vec[i][j]-b.vec[i][j];
}
}
return ret;
}

vector<matrix> divide()const{
matrix m1(n/2,m/2),m2(n/2,m-m/2),m3(n-n/2,m/2),m4(n-n/2,m-m/2);
for(int i=0;i<m1.n;i++){
for(int j=0;j<m1.m;j++){
m1.vec[i][j]=vec[i][j];
}
}
for(int i=0;i<m2.n;i++){
for(int j=0;j<m2.m;j++){
m2.vec[i][j]=vec[i][j+m1.m];
}
}
for(int i=0;i<m3.n;i++){
for(int j=0;j<m3.m;j++){
m3.vec[i][j]=vec[i+m1.n][j];
}
}
for(int i=0;i<m4.n;i++){
for(int j=0;j<m4.m;j++){
m4.vec[i][j]=vec[i+m1.n][j+m1.m];
}
}
return vector<matrix>{m1,m2,m3,m4};
}


};

matrix merge(vector<matrix> &matrices){
matrix ret(matrices[0].n+matrices[3].n,matrices[0].m+matrices[3].m);
for(int i=0;i<matrices[0].n;i++){
for(int j=0;j<matrices[0].m;j++){
ret.vec[i][j]=matrices[0].vec[i][j];
}
}
for(int i=0;i<matrices[1].n;i++){
for(int j=0;j<matrices[1].m;j++){
ret.vec[i][j+matrices[0].m]=matrices[1].vec[i][j];
}
}
for(int i=0;i<matrices[2].n;i++){
for(int j=0;j<matrices[2].m;j++){
ret.vec[i+matrices[0].n][j]=matrices[2].vec[i][j];
}
}
for(int i=0;i<matrices[3].n;i++){
for(int j=0;j<matrices[3].m;j++){
ret.vec[i+matrices[0].n][j+matrices[0].m]=matrices[3].vec[i][j];
}
}
return ret;
}

matrix strassen(matrix a,matrix b){
if(a.n<=75)return a*b;
if(a.n&1){
a.vec.push_back(vector<double>(a.m));
b.vec.push_back(vector<double>(b.m));
a.n++,b.n++;
}
if(a.m&1){
for(auto i:a.vec)i.push_back(0);
for(auto i:b.vec)i.push_back(0);
a.m++,b.m++;
}
// cout<<a.n<<" "<<a.m<<" "<<b.n<<" "<<b.m;
vector<matrix> vec1=a.divide(),vec2=b.divide();
// cout<<vec1[0].n<<" "<<vec2[3].m<<endl;
vector<matrix> m(7);
m[0]=strassen((vec1[0]+vec1[3]),(vec2[0]+vec2[3]));
m[1]=strassen((vec1[2]+vec1[3]),vec2[0]);
m[2]=strassen(vec1[0],(vec2[1]-vec2[3]));
m[3]=strassen(vec1[3],(vec2[2]-vec2[0]));
m[4]=strassen((vec1[0]+vec1[1]),vec2[3]);
m[5]=strassen((vec1[2]-vec1[0]),(vec2[0]+vec2[1]));
m[6]=strassen((vec1[1]-vec1[3]),(vec2[2]+vec2[3]));
vector<matrix> mm({m[0]+m[3]-m[4]+m[6],m[2]+m[4],m[1]+m[3],m[0]+m[2]-m[1]+m[5]});
return merge(mm);
}
int main(void){
//cout<<"Hello world\n";
matrix a(SIZE_OF_MATRIX,SIZE_OF_MATRIX),b(SIZE_OF_MATRIX,SIZE_OF_MATRIX);
a.get_rand(),b.get_rand();
//a.output(),b.output();

matrix c=strassen(a,b);
/*matrix d=a*b;
bool ok=true;
for(int i=0;i<SIZE_OF_MATRIX;i++){
for(int j=0;j<SIZE_OF_MATRIX;j++)if(abs(c.vec[i][j]-d.vec[i][j])>eps)ok=false,cout<<i<<" "<<j<<" "<<c.vec[i][j]<<" "<<d.vec[i][j]<<endl;
}*/
// c.output();
}

  考虑到函数调用会产生大量性能损失,我在strassen函数里设置了一个常量,当矩阵大小小于等于这个量时使用普通的矩阵乘法。
  对这个常量值为40,75,150(对应进行三层递归,两层递归,一层递归)分别进行了测试,运行效果如下:

image.png

image.png
image.png
  实验证明,进行两层递归的效果最好,为0.52秒左右,比直接计算有稍微的提高。
  同样是使用

1
perf stat -e cache-misses,instructions,cycles,mem-loads -r 10  ./a

  来对性能进行分析,结果如下图所示:
image.png
  cache-misses次数较多,CPI是0.347,比之前有所降低。

第二题

本来是打算做一下的,但没装双系统,只有一个linux服务器,又没有管理员权限,只能放弃了。

第三题

题目

  Consider a memory system with a level 1 cache of 32 KB and DRAM of 512 MB with the processor operating at 1 GHz. The latency to L1 cache is one cycle and the latency to DRAM is 100 cycles. In each memory cycle, the processor fetches four words (cache line size is four words). What is the peak achievable performance of a dot product of two vectors? Note: Where necessary, assume an optimal cache placement policy.

1
2
for (i = 0; i < dim; i++)
dot_prod += a[i] * b[i];

解答

  在这里我假设数组的元素都是4字节的float类型,一个word是4个字节,cache分配时不会把上一次刚分配的块覆盖掉,i,dot_prod两个变量放在寄存器里。
  则对每一次循环:
  ①如果i%4==0,则读取a[i]时需要从内存中取出a[i],a[i+1],a[i+2],a[i+3]放在cache里,需要100ns,读取b[i]时需要从内存中取出b[i],b[i+1],b[i+2],b[i+3]放在cache里,需要100ns。则这次循环需要 100ns+100ns+4ns(比较i与dim要1ns,i++要1ns,a[i]b[i]要1ns,将结果加到dot_prod要1ns),共204ns。
  ②如果i%4!=0,则a[i],b[i]可以从cache里取出,需要2ns。则这次循环需要 2ns+2ns+4ns,共8ns。
  所以,每次循环平均需要 $\frac{3
8+204}{4}=57ns$故每秒钟能进行17.5M次循环。考虑到每次循环是进行了两次浮点数运算,所以能达到的最高性能是35MFLOPS

第四题

题目

  Now consider the problem of multiplying a dense matrix with a vector using a two-loop dot-product formulation. The matrix is of dimension 4K x 4K. (Each row of the matrix takes 16 KB of storage.) What is the peak achievable performance of this technique using a twoloop dot-product based matrix-vector product.

1
2
3
for (i = 0; i < dim; i++)
for (j = 0; j < dim; j++)
c[i] += a[i][j] * b[j];

解答

  我们仍然假设cache不会把刚刚分配的块覆盖掉,在取a[i][j]时只需访问一次内存\cache,i和j两个变量放在寄存器里,且i<dim,i++这两个操作可以忽略不计。值得注意的是cache的大小为32k,而b数组大小为16k,矩阵a的一行也是16k,如果cache分配策略是最优的,则b数组会在i=0时全部装进cache里,只有a数组需要重复取出,所以在内存里取出b数组的时间也可以忽略。
  则对每一次循环:
  ①如果j%4==0,则读取a[i][j]时需要从内存中取出a[i][j],a[i][j+1],a[i][j+2],a[i][j+3]放在cache里,需要100ns。c[i]只有一开始会从内存中取到cache里(这一次的用时也忽略不计),之后一直在寄存器里放着。
则这次循环需要 100ns+5ns(假设比较j与dim要1ns,j++要1ns,a[i][j]b[j]要1ns,取出c[i]要2ns,将结果加到c[i]要1ns),共106ns。
  ②如果i%4!=0,则a[i][j],b[j]可以从cache里取出,需要2ns。则这次循环需要 2ns+2ns+6ns,共10ns。
  所以,每次循环平均需要 $\frac{3
10+106}{4}=34ns$,每秒钟能执行29.4M次循环,能达到的最高性能是59.8MFLOPS。

总结

  这次实验还是很有难度的。使用perf需要在linux系统,而我只有一个WSL,用不了perf,于是只能找人借了个linux服务器账号。好不容易完成了第一问,去钻研第二问的时候又遇到了很多不会的知识。折腾一番后总算明白要用到linux内核,但我没有管理员权限,用不了,只得作罢。虽然没能把第二问做出来,但多多少少还是有点收获。
  做到后面的时候发现第一问的矩阵乘法还有很多可以优化的地方,如vector改为数组、把数组改为指针等,但时间有限就没有修改了。

并行与分布式计算笔记2

主函数之外的变量是全局的,被所有线程共享,一般用于通信。
在堆上申请的,可能是共享的。
栈上的变量一般是私有的,只有某个线程能够使用。

线程是有限的,如果创建的线程太多,对操作系统而言是个灾难。
创建一个线程大概要1e4个cpu时钟,线程间切换大概要1e3个cpu时钟。

线程的调度取决于:
操作系统的硬件,如果线程太多会执行不过来。
调度器的设计。
程序员的干预,可以设定线程跑在哪个CPU核心。

有一些硬件本身支持多线程,线程间切换会快很多,大概10左右cpu时钟。
线程切换:
执行一定指令后切换(细粒度)
当线程遇到需要花很多时间的操作如cache miss切换,线程会off-cpu。‘;(粗粒度)

将ILP和TLP结合
TLP是将几个指令流同时进行,可以把这里的指令换成ILP的指令。
这种方法称为同时多线程(Simultaneous Multi-threading),Intel称之为超线程。

内存系统的latency和bandwidth
latency是请求的发出到返回的时间。
bandwidth是单位时间内能从内存中取出的数据量。

并行与分布式计算作业1

作业题目

现代处理器如Intel、ARM、AMD、Power以及国产CPU如华为鲲鹏等,均包含了并行指令集合,① 请调查这些处理器中的并行指 令集,并选择其中一种进行编程练习,计算两个各包含10^6个整数的向量之和。 此外,② 现代操作系统为了发挥多核的优势,支持多线程并行编程模型,请将问题①用多线程的方式实现,线程实现的语言不限,可以是Java,也可以是 C/C++。

用串行的方法完成向量之和计算

为了进行对照,先写一个串行的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<bits/stdc++.h>
#include<sys/time.h>
#define Size 1000000
using namespace std;

int a[Size],b[Size],c[Size];

int main(void){
struct timeval tv,tv2;
gettimeofday(&tv,NULL);
for(int i=0;i<Size;i++)c[i]=a[i]+b[i];
gettimeofday(&tv2,NULL);
printf("time: %d\n",tv2.tv_sec*1000000 + tv2.tv_usec - tv.tv_sec*1000000 - tv.tv_usec);

}

进行了5次实验,结果如下:
image.png
平均耗时:3,276.2μs

用并行指令集axv来完成向量之和计算

首先在https://github.com/chen0031/AVX-AVX2-Example-Code中下载文件夹,在wsl中cd进入文件夹,即可用make命令进行编译,用make run命令运行。

image.png

image.png

然后执行 cd ./Arithmetic_Intrinsics/src,将其中的add.c文件修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* Author: TripleZ<me@triplez.cn>
* Date: 2018-08-17
*/
#define Size 1000000
#include <immintrin.h>
#include <stdio.h>
#include <sys/time.h>
__m256i my_int_vec_0[Size/8+3];//数组每个元素存8个整数,故数组的大小为Size/8,+3是为了防止越界
__m256i my_int_vec_1[Size/8+3];
__m256i my_int_result[Size/8+3];
int main(int argc, char const *argv[]) {

for(int i=0;i<Size/8;i++){
for(int j=0;j<8;j++){
my_int_vec_0[i][j]=rand();
my_int_vec_1[i][j]=rand();
}
}
struct timeval tv,tv2;
gettimeofday(&tv,NULL);
for(int i=0;i<Size/8;i++){
my_int_result[i]=_mm256_add_epi32(my_int_vec_0[i],my_int_vec_1[i]);
}
gettimeofday(&tv2,NULL);
printf("\n\n\n\n\n\ntime: %dμs\n\n\n\n\n\n",tv2.tv_sec*1000000 + tv2.tv_usec - tv.tv_sec*1000000 - tv.tv_usec);
}

即可进行计算。

分别进行5次实验,耗时分别为(单位为μs):
1491,1329,1178,2009,1470
平均耗时:1,495.4μs
可以发现,用并行指令集进行计算会快很多,加速比大概是2.19

用多线程来完成向量之和计算

使用mpi来实现多线程完成向量之和,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include<sys/time.h>
#include <mpi.h>
#define MAX_size 10000000
using namespace std;
int a[MAX_size], b[MAX_size], c[MAX_size];
int main(int argc, char **argv)
{
//cout<<"???\n";
int numprocs, myid, source;
MPI_Status status;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
int sub_size=MAX_size / (numprocs - 1);
// cout<<"???\n";
if (myid == 0)
{
struct timeval tv,tv2;
gettimeofday(&tv,NULL);
//cout<<"???\n";
for (int i = 1; i < numprocs; i++)
{
MPI_Send(a + (i - 1) * sub_size, sub_size,
MPI_INT, i, i, MPI_COMM_WORLD);
MPI_Send(b + (i - 1) * sub_size, sub_size,
MPI_INT, i, i, MPI_COMM_WORLD);
}
// cout<<"???\n";
for (int i = 1; i < numprocs; i++)
{
MPI_Recv(c + (i - 1) * sub_size, sub_size,
MPI_INT, i, i, MPI_COMM_WORLD, &status);
}
gettimeofday(&tv2,NULL);
printf("time: %d\n",tv2.tv_sec*1000000 + tv2.tv_usec - tv.tv_sec*1000000 - tv.tv_usec);
}
else
{
int *tema = new int[sub_size];
int *temb = new int[sub_size];
int *temc = new int[sub_size];
MPI_Recv(tema, sub_size,
MPI_INT, 0, myid, MPI_COMM_WORLD, &status);
MPI_Recv(temb, sub_size,
MPI_INT, 0, myid, MPI_COMM_WORLD,&status);
for(int i=0;i<sub_size;i++)temc[i]=tema[i]+temb[i];
// cout<<"?????\n";
MPI_Send(temc,sub_size,MPI_INT,0,myid,MPI_COMM_WORLD);
}
MPI_Finalize();
}

分别对线程数量为3,5,6,9,11(即将数组分为2,4,5,8,10份)进行了实验,结果如下:

image.png

可以发现消耗的时间比前面用并行指令集多很多,这应该是因为线程之间通信次数很多,消耗了很多时间,而本身要计算的任务并不复杂,因此多线程的优势并不能得以发挥。

再使用OpenMP来进行多线程计算,这只需将串行的代码稍作修改即可。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<bits/stdc++.h>
#include<omp.h>
#include<sys/time.h>
using namespace std;
int x;


int sttoi(char* s){
int ret=0;
for(int i=0;s[i]!='\0';i++){
ret*=10;
ret+=s[i]-48;
}
return ret;
}
const int MAX=1e6;
int a[MAX],b[MAX],c[MAX];

int main(int argc,char** argv){
int num=sttoi(argv[1]);
struct timeval begin,end;
gettimeofday(&begin ,NULL);
#pragma omp parallel for num_threads(num)
for(int i=0;i<MAX;i++)c[i]=a[i]+b[i];
gettimeofday(&end ,NULL);
printf("%d\n",end.tv_sec*1000000+end.tv_usec-begin.tv_sec*1000000-begin.tv_usec);
}

对线程数分别为1-11做了5次实验,分别取平均值,绘图如下:
运行结果如下:
image.png
可以看到当线程数为4时,运行时间最短,大概是1500μs,当线程数增多时,运行时间变化并不规律。
绘制对应的加速比图像如下:
image.png
当线程数为4时,加速比大概是2.3,略强于用并行指令集的情况。

openmp学习笔记

主要参考这两篇博客:
https://blog.csdn.net/ArrowYL/article/details/81094837
https://blog.csdn.net/YUNXIN221/article/details/103964460
在此向两位博主表示感谢。

几种常用的子句

openmp 指令一般以 #pragma omp 开头,后面接其他的子句。常用子句有:

parallel

用于指定接下来的代码块被并行执行,如
image.png
但如果把大括号去掉,则只有第一句被并行执行:
image.png

for

for子句一般与parallel一起用,可以使一段for循环被分配到多线程处理。注意,用户需要保证for循环中没有数据依赖问题,否则会得到错误结果。
正确示范:
image.png

错误示范:

1
2
3
4
5
6
7
8
9
10
#include<bits/stdc++.h>
#include<omp.h>
using namespace std;
int main(void){
int fib[101]={1,1};
#pragma omp parallel for
for(int i=2;i<=20;i++)fib[i]=fib[i-1]+fib[i-2];

for(int i=0;i<=20;i++)printf("%d ",fib[i]);
}

结果为
image.png
可以看到,只有前几项是正确的结果,后面的数都是0,这是因为openmp将for循环分给多个线程执行,后面的线程执行的时候fib[i-1]和fib[i-2]还没被算出来。

此外,需注意的是使用for子句还对for循环有很多要求:
1/循环的变量变量(就是i)必须是整形,其他的(double等)都不行。
2.循环的比较条件必须是< <= > >=中的一种
3.循环的增量部分必须是增减一个不变的值(即每次循环是不变的)。
4.如果比较符号是< <=,那每次循环i应该增加,反之应该减小
5.循环必须是没有奇奇怪怪的东西,不能从内部循环跳到外部循环,goto和break只能在循环内部跳转,异常必须在循环内部被捕获。

sections和section

sections 子句将每个section分给一个线程执行,不同section之间是并行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<bits/stdc++.h>
#include<omp.h>
int main(void){
#pragma omp parallel sections//注意这里一定要换行不然会报错
{
#pragma omp section
{//这里也要换行
printf("section 1 threadid=%d\n",omp_get_thread_num());
}
#pragma omp section
{
printf("kjaenknfe\n");
}
#pragma omp section
{
printf("section 3 threadid=%d\n",omp_get_thread_num());
}
#pragma omp section
{
printf("section 4 threadid=%d\n",omp_get_thread_num());
}
}
}

private,firstprivate,lastprivate

private(x)子句为每个线程声明一个私有变量x,不同线程的x之间没有联系,即使前面的程序中有x这个变量也不会干扰。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<bits/stdc++.h>
#include<omp.h>
using namespace std;
int main(void){
int x=100;
cout<<"x="<<x<<endl;
#pragma omp parallel private(x)
{
x=omp_get_thread_num();
printf("In thread %d,x=%d\n",omp_get_thread_num(),x);
}
cout<<"x="<<x<<endl;
}

运行结果:
image.png
firstprivate(x)子句使子线程的x继承主线程中的x值,不同线程之间的x没有联系,而子线程中改变x的值并不会导致主线程中的x的值改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<bits/stdc++.h>
#include<omp.h>
using namespace std;
int main(void){
int x=100;
cout<<"x="<<x<<endl;
#pragma omp parallel firstprivate(x)
{
x+=omp_get_thread_num();
printf("In thread %d,x=%d\n",omp_get_thread_num(),x);
}
cout<<"x="<<x<<endl;
}

运行结果:
image.png
lastprivate(x)子句的作用与firstprivate相反,它为每个子线程中声明变量x,但x并不继承主程序中x的值。当子线程结束之后,把子线程的x的值赋给主线程中的x。

在for循环中使用lastprivate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<bits/stdc++.h>
#include<omp.h>
using namespace std;
int main(void){
int x=100;
cout<<"x="<<x<<endl;
#pragma omp parallel for firstprivate(x),lastprivate(x)
for(int i=0;i<20;i++){
x+=i;
printf("%d %d\n",omp_get_thread_num(),x);
}

cout<<"x="<<x<<endl;
}

注意,如果是在for循环中使用lastprivate(x),则会将最后一个线程的x的最终值赋给主变量中的x。这里的最后一个线程指的是逻辑上的最后一个,而不是最后一个结束的,如这里线程号为11的线程是最后一个,它的x的值最终是119,所以把119赋给主程序中的x。

image.png

在sections中使用lastprivate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<bits/stdc++.h>
#include<omp.h>
using namespace std;
int main(void){
int x=100;
cout<<"x="<<x<<endl;
#pragma omp parallel sections lastprivate(x)
{
#pragma omp section
{
x=omp_get_thread_num();
printf("section 1:x=%d\n",x);
}
#pragma omp section
{
x=omp_get_thread_num();
printf("section 2:x=%d\n",x);
}
#pragma omp section
{
x=omp_get_thread_num();
printf("section 3:x=%d\n",x);
}
#pragma omp section
{
x=omp_get_thread_num();
printf("section 4:x=%d\n",x);
}
}

cout<<"x="<<x<<endl;
}

而如果是在sections里使用,则会把最后一个section的x赋值回去。
image.png

threadprivate

threadprivate(x)将x(一般是全局变量)复制到各个子线程,在其他线程中修改x的值不会影响主程序中x的值,但第0个线程中修改x的值会导致主程序中x的值一并修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<bits/stdc++.h>
#include<omp.h>
using namespace std;
int global_x = 10;
#pragma omp threadprivate(global_x)
int main(void){
#pragma omp parallel for
for(int i=0;i<100;i++){
global_x=i;
}

#pragma omp parallel
printf("in thread%d: %d\n",omp_get_thread_num(),global_x);

cout<<"In main thread: "<<global_x<<endl;

}

运行结果:
image.png

copyin

copyin(x)等于把threadprivate变量x全局变量广播一遍,让所有线程的x值与主进程相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<omp.h>
#include<bits/stdc++.h>
using namespace std;
int A=100;
#pragma omp threadprivate(A)
int main(void){
omp_set_dynamic(0);
#pragma omp parallel for
for(int i=0;i<100;i++){
A=i;
}
#pragma omp parallel
{
printf("The value of A in thread without copyin %d: %d\n",omp_get_thread_num(),A);
}
#pragma omp parallel copyin(A)
{
printf("The value of A in thread %d: %d\n",omp_get_thread_num(),A);
}
}

运行结果:
image.png

openmp中的任务调度问题

对于一个for循环,编译器要考虑将不同的部分调度给不同的线程执行。默认情况下openmp大多采用静态调度方法,即假设循环需要进行n次,有k个线程,则前$n%k$个线程执行 $\left\lceil\dfrac{n}{k}\right\rceil$次,后$n-n%k$个线程执行$\left\lfloor\dfrac{n}{k}\right\rfloor$次。

OpenMP提供了schedule子句来实现任务的调度。schedule子句格式:schedule(type,[size])。

  参数type是指调度的类型,可以取值为static,dynamic,guided,runtime四种值。其中runtime允许在运行时确定调度类型,因此实际调度策略只有前面三种。

1 静态调度static
如果参数[size]为空,则采用默认的调度方式;否则将循环的前size次由第一个线程执行,size+1 —— 2size 次由第二个线程执行……第(k-1)size+1——k*size次由第k个线程执行。如果k*size<n,则再分配size次给第一个线程、分配size个给第二个线程……

1
2
3
4
5
6
7
8
9
#include<bits/stdc++.h>
#include<omp.h>
using namespace std;
int main(void){
#pragma omp parallel for schedule(static,3)
for(int i=0;i<50;i++){
printf("%d: Thread %d\n",i,omp_get_thread_num());
}
}

运行结果:
image.png

2 动态调度dynamic
动态调度依赖于运行时的状态动态确定线程所执行的迭代,也就是线程执行完已经分配的任务后,会去领取还有的任务(与静态调度最大的不同,每个线程完成的任务数量可能不一样)。由于线程启动和执行完的时间不确定,所以迭代被分配到哪个线程是无法事先知道的。

  当不使用size 时,是将迭代逐个地分配到各个线程。当使用size 时,逐个分配size个迭代给各个线程,这个用法类似静态调度。

3 启发式调度guided
  采用启发式调度方法进行调度,每次分配给线程迭代次数不同,开始比较大,以后逐渐减小。开始时每个线程会分配到较大的迭代块,之后分配到的迭代块会逐渐递减。迭代块的大小会按指数级下降到指定的size大小,如果没有指定size参数,那么迭代块大小最小会降到1。

  size表示每次分配的迭代次数的最小值,由于每次分配的迭代次数会逐渐减少,少到size时,将不再减少。具体采用哪一种启发式算法,需要参考具体的编译器和相关手册的信息。

一点小建议

尽量不要用在并行执行的程序块里cout。原因很简单,比如cout<<a<<b,可能第一个线程刚输出了a还没输出b,第二个线程由输出了a,就乱套了,比如下面的例子:

1
2
3
4
5
6
7
#include<bits/stdc++.h>
#include<omp.h>
using namespace std;
int main(void){
#pragma omp parallel for
for(int i=0;i<10;i++)cout<<"Thread:"<<omp_get_thread_num()<<"\n";
}

image.png

但如果是使用printf就这个问题:
image.png

如果要统计一段openmp并行化的程序的运行时间,不要用clock()函数。这是因为clock()得到的是cpu时间,会把多个核的时间加在一起;而我们想要的一般是现实中经过的时间,所以要用omp_get_wtime()函数。
举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<bits/stdc++.h>
#include<omp.h>
using namespace std;
int x;
void f(){
for(int i=0;i<300000000;i++);
}
int main(void){
double t1=omp_get_wtime();
#pragma omp parallel for
for(int i=0;i<12;i++)f();
double t2=omp_get_wtime();
cout<<t2-t1<<"s\n";

double t3=clock();
#pragma omp parallel for
for(int i=0;i<12;i++)f();
double t4=clock();
cout<<(t4-t3)/1e6<<"s\n";
}

结果如下:
image.png
用clock()统计出来的时间有6.9s,但事实上程序运行的时间不到2s。openmp在我的电脑上默认使用12线程,所以大概是clock()函数会把每个线程的时间都加起来。

计算机网络实验1_Echo实验

1.编写TCP Echo程序

  客户端向服务器发送一个字符串,服务器收到后向字符串里添加当前的时间,再发送回客户端。

客户端程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#define BUFLEN 2000 // 缓冲区大小
/*------------------------------------------------------------------------
* main - TCP client for TIME service
*------------------------------------------------------------------------
*/
int
main(int argc, char *argv[])
{
char *host = "127.0.0.1"; /* server IP to connect本地IP */
char *service = "50500"; /* server port to connect 端口号 */
struct sockaddr_in sin; /* an Internet endpoint address */
char buf[BUFLEN+1]; /* buffer for one line of text */
int sock; /* socket descriptor */
int cc; /* recv character count */

sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套接字,参数:因特网协议簇(family),流套接字,TCP协议
//返回:要监听套接字的描述符或INVALID_SOCKET
memset(&sin, 0, sizeof(sin)); // 从&sin开始的长度为sizeof(sin)的内存清0
sin.sin_family = AF_INET; // 因特网地址簇
sin.sin_addr.s_addr = inet_addr(host); // printf("%d\n",sin.sin_addr.s_addr); // 设置服务器IP地址(32位)
sin.sin_port = htons((unsigned short)atoi(service)); // 设置服务器端口号
int ret=connect(sock, (struct sockaddr *)&sin, sizeof(sin)); // 连接到服务器,第二个参数指向存放服务器地址的结构,第三个参数为该结构的大小,返回值为0时表示无错误发生,
printf("请输入要发送的消息:");
scanf("%s",buf);
int c1=send(sock,buf,strlen(buf),0);
cc = recv(sock, buf, BUFLEN, 0); // 第二个参数指向缓冲区,第三个参数为缓冲区大小(字节数),第四个参数一般设置为0,返回值:(>0)接收到的字节数,(=0)对方已关闭,(<0)连接出错
if(cc<=0)
printf("Error!\n"); //出错或对方关闭(==0)。其后必须关闭套接字sock。
else if(cc > 0) {
buf[cc] = '\0'; // ensure null-termination
printf("\n客户端:\n收到的消息:%s\n",buf); // 显示所接收的字符串
}
close(sock); // 关闭监听套接字

printf("按回车键继续...");
getchar(); // 等待任意按键
}

服务器程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/* server2.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

int main(int argc, char *argv[])
/* argc: 命令行参数个数, 例如:C:\> TCPServer 8080
argc=2 argv[0]="TCPServer",argv[1]="8080" */
{
struct sockaddr_in fsin; /* the from address of a client */
int msock, ssock; /* master & slave sockets */
char *service = "50500";
struct sockaddr_in sin; /* an Internet endpoint address */
int alen; /* from-address length */
char pts[1000]; /* pointer to time string */
time_t now; /* current time */

msock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); // 创建套接字,参数:因特网协议簇(family),流套接字,TCP协议
// 返回:要监听套接字的描述符或INVALID_SOCKET

memset(&sin, '\0', sizeof(sin)); // 从&sin开始的长度为sizeof(sin)的内存清0
sin.sin_family = AF_INET; // 因特网地址簇(INET-Internet)
sin.sin_addr.s_addr = INADDR_ANY; // 监听所有(接口的)IP地址。
sin.sin_port = htons((unsigned short)atoi(service)); // 监听的端口号。atoi--把ascii转化为int,htons--主机序到网络序(host to network,s-short 16位)
bind(msock, (struct sockaddr *)&sin, sizeof(sin)); // 绑定监听的IP地址和端口号

listen(msock, 5); // 建立长度为5的连接请求队列,并把到来的连接请求加入队列等待处理。

while (1)
{ // 检测是否有按键,如果没有则进入循环体执行
alen = sizeof(struct sockaddr); // 取到地址结构的长度
ssock = accept(msock, (struct sockaddr *)&fsin, (socklen_t *)&alen); // 如果在连接请求队列中有连接请求,则接受连接请求并建立连接,返回该连接的套接字,否则,本语句被阻塞直到队列非空。fsin包含客户端IP地址和端口号
time_t t = time(NULL);
char ch[64] = {0};
char result[100] = {0};
strftime(ch, sizeof(ch) - 1, "%Y-%m-%d--%H:%M:%S\n", localtime(&t));
int c1 = sprintf(pts, "%s",ch);
int c2 = recv(ssock, pts + c1, 1000, 0);
pts[c1 + c2] = '\0';
printf("\n服务器端:\n收到消息:%s\n收到消息的时间:%s", pts + c1,ch);
int cc = send(ssock, pts, strlen(pts), 0); // 第二个参数指向发送缓冲区,第三个参数为要发送的字节数,第四个参数一般置0,返回值为实际发送的字节数,出错或对方关闭时返回SOCKET_ERROR。
// printf("%s", pts); // 显示发送的字符串
close(ssock); // 关闭连接套接字
}
close(msock); // 关闭监听套接字
}

效果展示

客户端
image.png
image.png

服务器
image.png

2.编写TCP echo增强程序

  服务器在收到客户端的消息时显示服务器的当前时间、客户端的IP地址、客户端的端口号和客户端发来的信息,并把它们一并返回给客户端。
  客户端在发送消息后把服务器发回给它的消息显示出来。客户端程序与(1)同,不用修改
  要求服务器直接从accept()的参数fsin中得到客户端的IP地址和端口号。注:服务器获取IP地址后要求直接使用s_un_b的四个分量得到IP地址,不能使用函数inet_ntoa()转换IP地址。

客户端程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* client2.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>

#define BUFLEN 2000 // 缓冲区大小
/*------------------------------------------------------------------------
* main - TCP client for TIME service
*------------------------------------------------------------------------
*/
int
main(int argc, char *argv[])
{
char *host = "127.0.0.1"; /* server IP to connect */
char *service = "50500"; /* server port to connect */
struct sockaddr_in sin; /* an Internet endpoint address */
char buf[BUFLEN+1]; /* buffer for one line of text */
int sock; /* socket descriptor */
int cc; /* recv character count */

sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //创建套接字,参数:因特网协议簇(family),流套接字,TCP协议
//返回:要监听套接字的描述符或INVALID_SOCKET
memset(&sin, 0, sizeof(sin));// 从&sin开始的长度为sizeof(sin)的内存清0
sin.sin_family = AF_INET; // 因特网地址簇
sin.sin_addr.s_addr = inet_addr(host); // printf("%d\n",sin.sin_addr.s_addr); // 设置服务器IP地址(32位)
sin.sin_port = htons((unsigned short)atoi(service)); // 设置服务器端口号
int ret=connect(sock, (struct sockaddr *)&sin, sizeof(sin)); // 连接到服务器,第二个参数指向存放服务器地址的结构,第三个参数为该结构的大小,返回值为0时表示无错误发生,
printf("请输入要发送的消息:");
scanf("%s",buf);
int c1=send(sock,buf,strlen(buf),0);
cc = recv(sock, buf, BUFLEN, 0); // 第二个参数指向缓冲区,第三个参数为缓冲区大小(字节数),第四个参数一般设置为0,返回值:(>0)接收到的字节数,(=0)对方已关闭,(<0)连接出错
if(cc<=0)
printf("Error!\n"); //出错或对方关闭(==0)。其后必须关闭套接字sock。
else if(cc > 0) {
buf[cc] = '\0'; // ensure null-termination
printf("\n客户端:\n收到的消息:%s\n",buf); // 显示所接收的字符串
}
close(sock); // 关闭监听套接字

printf("按回车键继续...");
getchar(); // 等待任意按键
}

服务器程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>

int main(int argc, char *argv[])
/* argc: 命令行参数个数, 例如:C:\> TCPServer 8080
argc=2 argv[0]="TCPServer",argv[1]="8080" */
{
struct sockaddr_in fsin; /* the from address of a client */
int msock, ssock; /* master & slave sockets */
char *service = "50500";
struct sockaddr_in sin; /* an Internet endpoint address */
int alen; /* from-address length */
char pts[1000]; /* pointer to time string */
time_t now; /* current time */

msock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); // 创建套接字,参数:因特网协议簇(family),流套接字,TCP协议
// 返回:要监听套接字的描述符或INVALID_SOCKET

memset(&sin, '\0', sizeof(sin)); // 从&sin开始的长度为sizeof(sin)的内存清0
sin.sin_family = AF_INET; // 因特网地址簇(INET-Internet)
sin.sin_addr.s_addr = INADDR_ANY; // 监听所有(接口的)IP地址。
sin.sin_port = htons((unsigned short)atoi(service)); // 监听的端口号。atoi--把ascii转化为int,htons--主机序到网络序(host to network,s-short 16位)
bind(msock, (struct sockaddr *)&sin, sizeof(sin)); // 绑定监听的IP地址和端口号

listen(msock, 5); // 建立长度为5的连接请求队列,并把到来的连接请求加入队列等待处理。

while (1)
{ // 检测是否有按键,如果没有则进入循环体执行
alen = sizeof(struct sockaddr); // 取到地址结构的长度
ssock = accept(msock, (struct sockaddr *)&fsin, (socklen_t *)&alen); // 如果在连接请求队列中有连接请求,则接受连接请求并建立连接,返回该连接的套接字,否则,本语句被阻塞直到队列非空。fsin包含客户端IP地址和端口号
time_t t = time(NULL);
char ch[64] = {0};
char result[100] = {0};
strftime(ch, sizeof(ch) - 1, "%Y-%m-%d %H:%M:%S\n", localtime(&t));
memset(pts,0,sizeof(pts));
int c1 = recv(ssock, pts, 1000, 0);
/* int c1 = sprintf(pts + c2, "\n%s", fsin.sin_port,
fsin.sin_addr.S_un.S_un_b.s_b1,
fsin.sin_addr.S_un.S_un_b.s_b2,
fsin.sin_addr.S_un.S_un_b.s_b3,
fsin.sin_addr.S_un.S_un_b.s_b4,
);*/
unsigned int tem=fsin.sin_addr.s_addr;
printf("收到消息: %s\n收到时间: %s客户端IP地址: %d.%d.%d.%d\n 客户端端口号:%d\n"
,pts,ch,(tem<<24)>>24,(tem<<16)>>24,(tem<<8)>>24,tem>>24,fsin.sin_port);
char tempts[100];
strcpy(tempts,pts);
tempts[strlen(pts)]='\0';
sprintf(pts,"\n内容: %s\n时间: %s客户端IP地址: %d.%d.%d.%d\n客户端端口号: %d\n",
tempts,ch,(tem<<24)>>24,(tem<<16)>>24,(tem<<8)>>24,tem>>24,fsin.sin_port);
// pts[c1 + c2] = '\0';
// printf("\n服务器端:\n收到消息:%s\n收到消息的时间:%s", pts + c1, ch);
int cc = send(ssock, pts, strlen(pts), 0); // 第二个参数指向发送缓冲区,第三个参数为要发送的字节数,第四个参数一般置0,返回值为实际发送的字节数,出错或对方关闭时返回SOCKET_ERROR。
}
close(msock); // 关闭监听套接字
}

效果展示

客户端程序:
asf.png

服务器程序:
ang.png

3.编写UDP Echo增强程序

  
  完成Echo功能,即当客户端发来消息时,服务器显示出服务器的当前时间、客户端的IP、客户端的端口号和客户发来的信息,并把它们一并发回给客户端,客户端然后把它们显示出来。
  服务器可以直接从recvfrom()的参数from中得到客户端的IP地址和端口号,并且服务器用sendto()发回给客户端消息时可以直接用该参数from作为参数toAddr。可以使用inet_ntoa()转换客户端IP地址。
  客户端程序的recvfrom()可以直接使用原来sendto使用的sock。该sock已经绑定了客户端的IP地址和端口号,客户端可以直接用来接收数据。

客户端程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#include <netinet/in.h>
#define DEFAULT_IP "127.0.0.1"
#define DEFAULT_PORT "54321"

int main()
{
char buf[1024];
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(DEFAULT_PORT));
local.sin_addr.s_addr = inet_addr(DEFAULT_IP);
socklen_t len = sizeof(local);
int sock = socket(AF_INET, SOCK_DGRAM, 0);
printf("输入消息:\n");
scanf("%s", buf);
int s1 = sendto(sock, buf, strlen(buf), 0, (struct sockaddr *)&local, len);
int r = recvfrom(sock, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&local, &len);
printf("%s\n ", buf);
printf("按任意键退出:\n");
getchar();
return 0;
}

服务器程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <arpa/inet.h>
#include <string.h>
#include <netinet/in.h>
#define DEFAULT_IP "127.0.0.1"
#define DEFAULT_PORT "54321"

int main()
{
int sock = socket(AF_INET, SOCK_DGRAM, 0);
char buf[1024];
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(DEFAULT_PORT));
local.sin_addr.s_addr = inet_addr(DEFAULT_IP);
bind(sock, (struct sockaddr *)&local, sizeof(local));
while (1)
{

struct sockaddr_in client;

socklen_t len = sizeof(client);
// printf("get a new client ,%s:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
int r = recvfrom(sock, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&client, &len);
// puts("??");
time_t t = time(NULL);
char ch[64] = {0};
strftime(ch, sizeof(ch) - 1, "%Y-%m-%d %H:%M:%S\n", localtime(&t));
unsigned int tem = client.sin_addr.s_addr;
char tempts[1024];
strcpy(tempts, buf);
// printf("%d\n",strlen(buf));
tempts[strlen(buf)] = '\0';
r = sprintf(buf, "\n客户端的消息: %s\n客户端IP地址: %d.%d.%d.%d\n时间: %s客户端端口号: %d\n",
tempts, (tem << 24) >> 24, (tem << 16) >> 24, (tem << 8) >> 24, tem >> 24, ch, client.sin_port);
// puts("??");
buf[r] = '\0';
printf("%s", buf);
int s = sendto(sock, buf, r, 0, (struct sockaddr *)&client, len);
memset(buf, 0, sizeof(buf));
}
return 0;
}

效果展示

客户端程序
aw.png

服务器程序
gweew.png

并行与分布式计算笔记1

计算节点用InfiniBand网络进行通信,传输速率可以到100GB/s。

并行编译器只能做到执行级的并行,检测到basic block和少量的嵌入的for循环,无法满足并行计算的要求。
所以需要让程序员来编写并行程序。

通过操作系统底层的线程库来编程会非常复杂,甚至出错,所以会有并行编程的工具来进行抽象。

常见的工具有:
OpenMP
Message Passing InterfaceMPI(mpi)
Posix Threads(pthreads)
Open Computing Language(opencl)
CUDA
Map Reduce

并行编程的难点

1找到尽可能多的并行点
2粒度:函数级并行、线程级并行还是进程级并行
3局部性:并行化后是否能够利用局部数据等
4负载均衡:不同线程、不同core之间的负载分布
5协作与同步
6性能模型

任务并行

将一个问题划分成多个不同的子任务在不同的core上运行;
(计算密集型,可以拆分成不同的子任务)

数据并行

1.将问题所涉及的数据才分到不同的core上执行;
2.每个core运行的是相同的程序。
(数据密集型,大数据应用)

下载WSL

WSL(Windows Subsystem for Linux)可以在windows中装一个Linux子系统,非常方便且易于使用。

首先在Microsoft Store中下载Ubuntu:

image.png

点开下载的界面会花很长时间,但下载速度很快。

下载过程中,打开Windows功能,勾选”适用于 Linux 的 Windows 子系统”:

image.png

然后打开下载好的Ubuntu,第一次打开要设定用户名和密码。这样,wsl就算是安装完了。

为了加快下载速度,把默认源切换成阿里源:

1
2
3
sudo sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list
sudo apt update -y
sudo apt upgrade -y

下载并配置VSCode

下载VSCode只用4条命令:

1
2
3
4
sudo add-apt-repository ppa:ubuntu-desktop/ubuntu-make
sudo apt update
sudo apt install ubuntu-make
sudo umake ide visual-studio-code

这样就得到一个未配置的VSCode了。
但是由于种种原因,下载下来的VSCode可能用不了。解决办法是在本地有一个能用的VSCode,然后在本地上安装Remote-WSL插件:

image.png

这样就可以在wsl里用code .(注意小数点和前面的空格不能少)命令进入VSCode了。在root状态下使用这个命令会失效,
而且再切回原本的用户再使用也会失效,这时只能关闭终端重新启动。

然后要在wsl里安装g++和gdb:

1
2
sudo apt-get install gdb
sudo apt-get install g++

接下来要配置开发环境,需要新建一个.vscode文件夹:
image.png

4个文件的内容分别是:

c_cpp_properties.json

1
2
3
4
5
6
7
8
9
10
11
12
{
"configurations": [
{
"name": "Linux",
"includePath": [],
"defines": ["_DEBUG","_UNICODE"],
"compilerPath": "/usr/bin/g++",
"intelliSenseMode": "${default}"
}
],
"version": 4
}

launch.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [

{
"name": "g++ build and debug active file",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}/${fileBasenameNoExtension}",
"args": [],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "g++ build active file",
"miDebuggerPath": "/usr/bin/gdb"
}
]
}

settings.json

1
2
3
4
5
{
"files.associations": {
"iostream": "cpp"
}
}

task.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"tasks": [
{
"type": "shell",
"label": "g++ build active file",
"command": "/usr/bin/g++",
"args": [
"-g",
"${file}",
"-o",
"${fileDirname}/${fileBasenameNoExtension}"
],
"options": {
"cwd": "/usr/bin"
}
}
],
"version": "2.0.0"
}

然后就可以写一个helloworld.cpp,愉快地一键F5运行了:
但还是会在上方出现一个选择环境的提示,选C++(GDB/LLDB)
选择配置则是g++ build and debug active file

image.png

然后会在终端看到结果:

image.png

当然,也可以在终端用g++直接编译然后运行~