Commit 5eb9cddd authored by Iker Martín's avatar Iker Martín
Browse files

Version 2 de aplicación

parent 2663ec23
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import pandas as pd\n",
"from pandas import DataFrame, Series\n",
"import numpy as np\n",
"import seaborn as sns\n",
"import matplotlib.pyplot as plt\n",
"from scipy import stats\n",
"import sys"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [],
"source": [
"matrixMalEX=\"data_GG.csv\"\n",
"matrixMal=\"data_GM.csv\"\n",
"matrixIt=\"data_L.csv\"\n",
"n_qty=2 #CAMBIAR SEGUN LA CANTIDAD DE NODOS USADOS\n",
"repet = 3 * 2 #CAMBIAR EL PRIMER NUMERO SEGUN NUMERO DE EJECUCIONES POR CONFIG\n",
"\n",
"p_value = 0.05\n",
"values = [2, 4, 8, 16, 32]\n",
"dist_names = ['null', 'BestFit', 'WorstFit']"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"def speedUp(arr, seq, df):\n",
" numP = df.loc[arr.index[0]].NP\n",
" return seq[( seq.NP == numP )]['EX'] / arr.mean()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"dfG = pd.read_csv( matrixMalEX )\n",
"\n",
"dfG = dfG.drop(columns=dfG.columns[0])\n",
"dfG['S'] = dfG['N']\n",
"dfG['N'] = dfG['S'] + dfG['%Async']\n",
"dfG['%Async'] = (dfG['%Async'] / dfG['N']) * 100\n",
"\n",
"if(n_qty == 1):\n",
" group = dfG.groupby(['%Async', 'Groups'])['TE']\n",
"else: \n",
" group = dfG.groupby(['Dist', '%Async', 'Groups'])['TE']\n",
"\n",
"#group\n",
"grouped_aggG = group.agg(['mean'])\n",
"grouped_aggG.rename(columns={'mean':'TE',}, inplace=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from natsort import index_natsorted\n",
"grouped_aggG.sort_values(\n",
" by=\"Groups\",\n",
" key=lambda x: np.argsort(index_natsorted(df[\"Groups\"]))\n",
")\n",
"grouped_aggG"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"dfM = pd.read_csv( matrixMal )\n",
"dfM = dfM.drop(columns=dfM.columns[0])\n",
"\n",
"dfM['S'] = dfM['N']\n",
"dfM['N'] = dfM['S'] + dfM['%Async']\n",
"dfM[\"TR\"] = dfM[\"TC\"] + dfM[\"TS\"] + dfM[\"TA\"]\n",
"dfM['%Async'] = (dfM['%Async'] / dfM['N']) * 100\n",
"\n",
"if(n_qty == 1):\n",
" groupM = dfM.groupby(['%Async','NP', 'NS'])['TC', 'TS', 'TA', 'TR']\n",
"else:\n",
" groupM = dfM.groupby(['Dist', '%Async','NP', 'NS'])['TC', 'TS', 'TA', 'TR']\n",
"\n",
"#group\n",
"grouped_aggM = groupM.agg(['mean'])\n",
"grouped_aggM.columns = grouped_aggM.columns.get_level_values(0)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:16: FutureWarning: set_axis currently defaults to operating inplace.\n",
"This will change in a future version of pandas, use inplace=True to avoid this warning.\n",
" app.launch_new_instance()\n"
]
}
],
"source": [
"dfL = pd.read_csv( matrixIt )\n",
"dfL = dfL.drop(columns=dfL.columns[0])\n",
"\n",
"dfL['S'] = dfL['N']\n",
"dfL['N'] = dfL['S'] + dfL['%Async']\n",
"dfL['%Async'] = (dfL['%Async'] / dfL['N']) * 100\n",
"\n",
"if(n_qty == 1):\n",
" groupL = dfL[dfL['NS'] != 0].groupby(['Tt', '%Async', 'NP', 'NS'])['Ti', 'To']\n",
"else:\n",
" groupL = dfL[dfL['NS'] != 0].groupby(['Tt', 'Dist', '%Async', 'NP', 'NS'])['Ti', 'To']\n",
"\n",
"#group\n",
"grouped_aggL = groupL.agg(['mean', 'count'])\n",
"grouped_aggL.columns = grouped_aggL.columns.get_level_values(0)\n",
"grouped_aggL.set_axis(['Ti', 'Iters', 'To', 'Iters2'], axis='columns')\n",
"\n",
"grouped_aggL['Iters'] = np.ceil(grouped_aggL['Iters']/6) # TODO Cambiar a repeticiones realizadas\n",
"grouped_aggL['Iters2'] = np.ceil(grouped_aggL['Iters2']/6)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"grouped_aggL.to_excel(\"resultL.xlsx\") \n",
"grouped_aggM.to_excel(\"resultM.xlsx\") \n",
"grouped_aggG.to_excel(\"resultG.xlsx\") "
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>N</th>\n",
" <th>%Async</th>\n",
" <th>Groups</th>\n",
" <th>Dist</th>\n",
" <th>Matrix</th>\n",
" <th>Time</th>\n",
" <th>Iters</th>\n",
" <th>TE</th>\n",
" <th>S</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <td>0</td>\n",
" <td>1000000000</td>\n",
" <td>0.0</td>\n",
" <td>8,32</td>\n",
" <td>2,2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>31.525710</td>\n",
" <td>1000000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>1</td>\n",
" <td>1000000000</td>\n",
" <td>0.0</td>\n",
" <td>8,32</td>\n",
" <td>2,2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>33.315857</td>\n",
" <td>1000000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>2</td>\n",
" <td>1000000000</td>\n",
" <td>0.0</td>\n",
" <td>8,32</td>\n",
" <td>2,2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>33.347537</td>\n",
" <td>1000000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>3</td>\n",
" <td>1000000000</td>\n",
" <td>75.0</td>\n",
" <td>4,32</td>\n",
" <td>1,1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>57.219027</td>\n",
" <td>250000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>4</td>\n",
" <td>1000000000</td>\n",
" <td>75.0</td>\n",
" <td>4,32</td>\n",
" <td>1,1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>55.469166</td>\n",
" <td>250000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <td>595</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>4,8</td>\n",
" <td>2,2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>73.771357</td>\n",
" <td>500000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>596</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>4,8</td>\n",
" <td>2,2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>75.557508</td>\n",
" <td>500000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>597</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>16,8</td>\n",
" <td>1,1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>35.949913</td>\n",
" <td>500000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>598</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>16,8</td>\n",
" <td>1,1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>37.900348</td>\n",
" <td>500000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>599</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>16,8</td>\n",
" <td>1,1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>36.012772</td>\n",
" <td>500000000</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>600 rows × 9 columns</p>\n",
"</div>"
],
"text/plain": [
" N %Async Groups Dist Matrix Time Iters TE \\\n",
"0 1000000000 0.0 8,32 2,2 100000 0.1 1000 31.525710 \n",
"1 1000000000 0.0 8,32 2,2 100000 0.1 1000 33.315857 \n",
"2 1000000000 0.0 8,32 2,2 100000 0.1 1000 33.347537 \n",
"3 1000000000 75.0 4,32 1,1 100000 0.1 1000 57.219027 \n",
"4 1000000000 75.0 4,32 1,1 100000 0.1 1000 55.469166 \n",
".. ... ... ... ... ... ... ... ... \n",
"595 1000000000 50.0 4,8 2,2 100000 0.1 1000 73.771357 \n",
"596 1000000000 50.0 4,8 2,2 100000 0.1 1000 75.557508 \n",
"597 1000000000 50.0 16,8 1,1 100000 0.1 1000 35.949913 \n",
"598 1000000000 50.0 16,8 1,1 100000 0.1 1000 37.900348 \n",
"599 1000000000 50.0 16,8 1,1 100000 0.1 1000 36.012772 \n",
"\n",
" S \n",
"0 1000000000 \n",
"1 1000000000 \n",
"2 1000000000 \n",
"3 250000000 \n",
"4 250000000 \n",
".. ... \n",
"595 500000000 \n",
"596 500000000 \n",
"597 500000000 \n",
"598 500000000 \n",
"599 500000000 \n",
"\n",
"[600 rows x 9 columns]"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dfG"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th>TE</th>\n",
" </tr>\n",
" <tr>\n",
" <th>Dist</th>\n",
" <th>%Async</th>\n",
" <th>Groups</th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <td rowspan=\"5\" valign=\"top\">1,1</td>\n",
" <td rowspan=\"5\" valign=\"top\">0.0</td>\n",
" <td>16,2</td>\n",
" <td>112.087769</td>\n",
" </tr>\n",
" <tr>\n",
" <td>16,32</td>\n",
" <td>18.394706</td>\n",
" </tr>\n",
" <tr>\n",
" <td>16,4</td>\n",
" <td>61.887052</td>\n",
" </tr>\n",
" <tr>\n",
" <td>16,8</td>\n",
" <td>37.986463</td>\n",
" </tr>\n",
" <tr>\n",
" <td>2,16</td>\n",
" <td>113.187865</td>\n",
" </tr>\n",
" <tr>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <td rowspan=\"5\" valign=\"top\">2,2</td>\n",
" <td rowspan=\"5\" valign=\"top\">100.0</td>\n",
" <td>4,8</td>\n",
" <td>74.864688</td>\n",
" </tr>\n",
" <tr>\n",
" <td>8,16</td>\n",
" <td>37.999586</td>\n",
" </tr>\n",
" <tr>\n",
" <td>8,2</td>\n",
" <td>123.584176</td>\n",
" </tr>\n",
" <tr>\n",
" <td>8,32</td>\n",
" <td>31.730297</td>\n",
" </tr>\n",
" <tr>\n",
" <td>8,4</td>\n",
" <td>74.052463</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>200 rows × 1 columns</p>\n",
"</div>"
],
"text/plain": [
" TE\n",
"Dist %Async Groups \n",
"1,1 0.0 16,2 112.087769\n",
" 16,32 18.394706\n",
" 16,4 61.887052\n",
" 16,8 37.986463\n",
" 2,16 113.187865\n",
"... ...\n",
"2,2 100.0 4,8 74.864688\n",
" 8,16 37.999586\n",
" 8,2 123.584176\n",
" 8,32 31.730297\n",
" 8,4 74.052463\n",
"\n",
"[200 rows x 1 columns]"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"grouped_aggG"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>N</th>\n",
" <th>%Async</th>\n",
" <th>NP</th>\n",
" <th>NS</th>\n",
" <th>Dist</th>\n",
" <th>Matrix</th>\n",
" <th>Time</th>\n",
" <th>Iters</th>\n",
" <th>TC</th>\n",
" <th>TS</th>\n",
" <th>TA</th>\n",
" <th>S</th>\n",
" <th>TR</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <td>0</td>\n",
" <td>1000000000</td>\n",
" <td>0.0</td>\n",
" <td>8</td>\n",
" <td>32</td>\n",
" <td>2,2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>1.341434</td>\n",
" <td>0.695848</td>\n",
" <td>0.000000</td>\n",
" <td>1000000000</td>\n",
" <td>2.037282</td>\n",
" </tr>\n",
" <tr>\n",
" <td>1</td>\n",
" <td>1000000000</td>\n",
" <td>0.0</td>\n",
" <td>8</td>\n",
" <td>32</td>\n",
" <td>2,2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>1.405911</td>\n",
" <td>0.639849</td>\n",
" <td>0.000000</td>\n",
" <td>1000000000</td>\n",
" <td>2.045760</td>\n",
" </tr>\n",
" <tr>\n",
" <td>2</td>\n",
" <td>1000000000</td>\n",
" <td>0.0</td>\n",
" <td>8</td>\n",
" <td>32</td>\n",
" <td>2,2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>1.346017</td>\n",
" <td>0.747863</td>\n",
" <td>0.000000</td>\n",
" <td>1000000000</td>\n",
" <td>2.093880</td>\n",
" </tr>\n",
" <tr>\n",
" <td>3</td>\n",
" <td>1000000000</td>\n",
" <td>75.0</td>\n",
" <td>4</td>\n",
" <td>32</td>\n",
" <td>1,1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.688062</td>\n",
" <td>0.064900</td>\n",
" <td>0.364941</td>\n",
" <td>250000000</td>\n",
" <td>1.117903</td>\n",
" </tr>\n",
" <tr>\n",
" <td>4</td>\n",
" <td>1000000000</td>\n",
" <td>75.0</td>\n",
" <td>4</td>\n",
" <td>32</td>\n",
" <td>1,1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.702200</td>\n",
" <td>0.068039</td>\n",
" <td>0.412072</td>\n",
" <td>250000000</td>\n",
" <td>1.182311</td>\n",
" </tr>\n",
" <tr>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <td>595</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>4</td>\n",
" <td>8</td>\n",
" <td>2,2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.390444</td>\n",
" <td>0.142287</td>\n",
" <td>0.338576</td>\n",
" <td>500000000</td>\n",
" <td>0.871307</td>\n",
" </tr>\n",
" <tr>\n",
" <td>596</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>4</td>\n",
" <td>8</td>\n",
" <td>2,2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.351690</td>\n",
" <td>0.154699</td>\n",
" <td>0.354917</td>\n",
" <td>500000000</td>\n",
" <td>0.861306</td>\n",
" </tr>\n",
" <tr>\n",
" <td>597</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>16</td>\n",
" <td>8</td>\n",
" <td>1,1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.337472</td>\n",
" <td>0.081077</td>\n",
" <td>0.243481</td>\n",
" <td>500000000</td>\n",
" <td>0.662030</td>\n",
" </tr>\n",
" <tr>\n",
" <td>598</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>16</td>\n",
" <td>8</td>\n",
" <td>1,1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.351262</td>\n",
" <td>0.103176</td>\n",
" <td>0.261565</td>\n",
" <td>500000000</td>\n",
" <td>0.716003</td>\n",
" </tr>\n",
" <tr>\n",
" <td>599</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>16</td>\n",
" <td>8</td>\n",
" <td>1,1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.349151</td>\n",
" <td>0.094121</td>\n",
" <td>0.221750</td>\n",
" <td>500000000</td>\n",
" <td>0.665022</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>600 rows × 13 columns</p>\n",
"</div>"
],
"text/plain": [
" N %Async NP NS Dist Matrix Time Iters TC TS \\\n",
"0 1000000000 0.0 8 32 2,2 100000 0.1 1000 1.341434 0.695848 \n",
"1 1000000000 0.0 8 32 2,2 100000 0.1 1000 1.405911 0.639849 \n",
"2 1000000000 0.0 8 32 2,2 100000 0.1 1000 1.346017 0.747863 \n",
"3 1000000000 75.0 4 32 1,1 100000 0.1 1000 0.688062 0.064900 \n",
"4 1000000000 75.0 4 32 1,1 100000 0.1 1000 0.702200 0.068039 \n",
".. ... ... .. .. ... ... ... ... ... ... \n",
"595 1000000000 50.0 4 8 2,2 100000 0.1 1000 0.390444 0.142287 \n",
"596 1000000000 50.0 4 8 2,2 100000 0.1 1000 0.351690 0.154699 \n",
"597 1000000000 50.0 16 8 1,1 100000 0.1 1000 0.337472 0.081077 \n",
"598 1000000000 50.0 16 8 1,1 100000 0.1 1000 0.351262 0.103176 \n",
"599 1000000000 50.0 16 8 1,1 100000 0.1 1000 0.349151 0.094121 \n",
"\n",
" TA S TR \n",
"0 0.000000 1000000000 2.037282 \n",
"1 0.000000 1000000000 2.045760 \n",
"2 0.000000 1000000000 2.093880 \n",
"3 0.364941 250000000 1.117903 \n",
"4 0.412072 250000000 1.182311 \n",
".. ... ... ... \n",
"595 0.338576 500000000 0.871307 \n",
"596 0.354917 500000000 0.861306 \n",
"597 0.243481 500000000 0.662030 \n",
"598 0.261565 500000000 0.716003 \n",
"599 0.221750 500000000 0.665022 \n",
"\n",
"[600 rows x 13 columns]"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dfM"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th>TC</th>\n",
" <th>TS</th>\n",
" <th>TA</th>\n",
" <th>TR</th>\n",
" </tr>\n",
" <tr>\n",
" <th>Dist</th>\n",
" <th>%Async</th>\n",
" <th>NP</th>\n",
" <th>NS</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <td rowspan=\"5\" valign=\"top\">1,1</td>\n",
" <td rowspan=\"5\" valign=\"top\">0.0</td>\n",
" <td rowspan=\"4\" valign=\"top\">2</td>\n",
" <td>4</td>\n",
" <td>0.220380</td>\n",
" <td>0.314893</td>\n",
" <td>0.000000</td>\n",
" <td>0.535273</td>\n",
" </tr>\n",
" <tr>\n",
" <td>8</td>\n",
" <td>0.248143</td>\n",
" <td>0.375003</td>\n",
" <td>0.000000</td>\n",
" <td>0.623146</td>\n",
" </tr>\n",
" <tr>\n",
" <td>16</td>\n",
" <td>0.339042</td>\n",
" <td>0.486650</td>\n",
" <td>0.000000</td>\n",
" <td>0.825692</td>\n",
" </tr>\n",
" <tr>\n",
" <td>32</td>\n",
" <td>0.743840</td>\n",
" <td>0.506385</td>\n",
" <td>0.000000</td>\n",
" <td>1.250225</td>\n",
" </tr>\n",
" <tr>\n",
" <td>4</td>\n",
" <td>2</td>\n",
" <td>0.197858</td>\n",
" <td>0.311136</td>\n",
" <td>0.000000</td>\n",
" <td>0.508994</td>\n",
" </tr>\n",
" <tr>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <td rowspan=\"5\" valign=\"top\">2,2</td>\n",
" <td rowspan=\"5\" valign=\"top\">100.0</td>\n",
" <td>16</td>\n",
" <td>32</td>\n",
" <td>1.358891</td>\n",
" <td>0.000000</td>\n",
" <td>1.419016</td>\n",
" <td>2.777907</td>\n",
" </tr>\n",
" <tr>\n",
" <td rowspan=\"4\" valign=\"top\">32</td>\n",
" <td>2</td>\n",
" <td>0.591882</td>\n",
" <td>0.000000</td>\n",
" <td>2.477300</td>\n",
" <td>3.069183</td>\n",
" </tr>\n",
" <tr>\n",
" <td>4</td>\n",
" <td>0.804965</td>\n",
" <td>0.000000</td>\n",
" <td>1.729638</td>\n",
" <td>2.534603</td>\n",
" </tr>\n",
" <tr>\n",
" <td>8</td>\n",
" <td>0.935128</td>\n",
" <td>0.000000</td>\n",
" <td>1.520682</td>\n",
" <td>2.455810</td>\n",
" </tr>\n",
" <tr>\n",
" <td>16</td>\n",
" <td>1.221248</td>\n",
" <td>0.000000</td>\n",
" <td>1.541904</td>\n",
" <td>2.763152</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>200 rows × 4 columns</p>\n",
"</div>"
],
"text/plain": [
" TC TS TA TR\n",
"Dist %Async NP NS \n",
"1,1 0.0 2 4 0.220380 0.314893 0.000000 0.535273\n",
" 8 0.248143 0.375003 0.000000 0.623146\n",
" 16 0.339042 0.486650 0.000000 0.825692\n",
" 32 0.743840 0.506385 0.000000 1.250225\n",
" 4 2 0.197858 0.311136 0.000000 0.508994\n",
"... ... ... ... ...\n",
"2,2 100.0 16 32 1.358891 0.000000 1.419016 2.777907\n",
" 32 2 0.591882 0.000000 2.477300 3.069183\n",
" 4 0.804965 0.000000 1.729638 2.534603\n",
" 8 0.935128 0.000000 1.520682 2.455810\n",
" 16 1.221248 0.000000 1.541904 2.763152\n",
"\n",
"[200 rows x 4 columns]"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"grouped_aggM"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>N</th>\n",
" <th>%Async</th>\n",
" <th>NP</th>\n",
" <th>N_par</th>\n",
" <th>NS</th>\n",
" <th>Dist</th>\n",
" <th>Matrix</th>\n",
" <th>Time</th>\n",
" <th>Iters</th>\n",
" <th>Ti</th>\n",
" <th>Tt</th>\n",
" <th>To</th>\n",
" <th>S</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <td>0</td>\n",
" <td>1000000000</td>\n",
" <td>0.0</td>\n",
" <td>32</td>\n",
" <td>8</td>\n",
" <td>0</td>\n",
" <td>2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.005463</td>\n",
" <td>0.0</td>\n",
" <td>6.0</td>\n",
" <td>1000000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>1</td>\n",
" <td>1000000000</td>\n",
" <td>0.0</td>\n",
" <td>32</td>\n",
" <td>8</td>\n",
" <td>0</td>\n",
" <td>2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.005350</td>\n",
" <td>0.0</td>\n",
" <td>6.0</td>\n",
" <td>1000000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>2</td>\n",
" <td>1000000000</td>\n",
" <td>0.0</td>\n",
" <td>32</td>\n",
" <td>8</td>\n",
" <td>0</td>\n",
" <td>2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.005355</td>\n",
" <td>0.0</td>\n",
" <td>6.0</td>\n",
" <td>1000000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>3</td>\n",
" <td>1000000000</td>\n",
" <td>0.0</td>\n",
" <td>32</td>\n",
" <td>8</td>\n",
" <td>0</td>\n",
" <td>2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.005354</td>\n",
" <td>0.0</td>\n",
" <td>6.0</td>\n",
" <td>1000000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>4</td>\n",
" <td>1000000000</td>\n",
" <td>0.0</td>\n",
" <td>32</td>\n",
" <td>8</td>\n",
" <td>0</td>\n",
" <td>2</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.005352</td>\n",
" <td>0.0</td>\n",
" <td>6.0</td>\n",
" <td>1000000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <td>1199995</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>8</td>\n",
" <td>16</td>\n",
" <td>0</td>\n",
" <td>1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.024075</td>\n",
" <td>0.0</td>\n",
" <td>27.0</td>\n",
" <td>500000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>1199996</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>8</td>\n",
" <td>16</td>\n",
" <td>0</td>\n",
" <td>1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.024076</td>\n",
" <td>0.0</td>\n",
" <td>27.0</td>\n",
" <td>500000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>1199997</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>8</td>\n",
" <td>16</td>\n",
" <td>0</td>\n",
" <td>1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.024076</td>\n",
" <td>0.0</td>\n",
" <td>27.0</td>\n",
" <td>500000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>1199998</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>8</td>\n",
" <td>16</td>\n",
" <td>0</td>\n",
" <td>1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.024411</td>\n",
" <td>0.0</td>\n",
" <td>27.0</td>\n",
" <td>500000000</td>\n",
" </tr>\n",
" <tr>\n",
" <td>1199999</td>\n",
" <td>1000000000</td>\n",
" <td>50.0</td>\n",
" <td>8</td>\n",
" <td>16</td>\n",
" <td>0</td>\n",
" <td>1</td>\n",
" <td>100000</td>\n",
" <td>0.1</td>\n",
" <td>1000</td>\n",
" <td>0.024075</td>\n",
" <td>0.0</td>\n",
" <td>27.0</td>\n",
" <td>500000000</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>1200000 rows × 13 columns</p>\n",
"</div>"
],
"text/plain": [
" N %Async NP N_par NS Dist Matrix Time Iters \\\n",
"0 1000000000 0.0 32 8 0 2 100000 0.1 1000 \n",
"1 1000000000 0.0 32 8 0 2 100000 0.1 1000 \n",
"2 1000000000 0.0 32 8 0 2 100000 0.1 1000 \n",
"3 1000000000 0.0 32 8 0 2 100000 0.1 1000 \n",
"4 1000000000 0.0 32 8 0 2 100000 0.1 1000 \n",
"... ... ... .. ... .. ... ... ... ... \n",
"1199995 1000000000 50.0 8 16 0 1 100000 0.1 1000 \n",
"1199996 1000000000 50.0 8 16 0 1 100000 0.1 1000 \n",
"1199997 1000000000 50.0 8 16 0 1 100000 0.1 1000 \n",
"1199998 1000000000 50.0 8 16 0 1 100000 0.1 1000 \n",
"1199999 1000000000 50.0 8 16 0 1 100000 0.1 1000 \n",
"\n",
" Ti Tt To S \n",
"0 0.005463 0.0 6.0 1000000000 \n",
"1 0.005350 0.0 6.0 1000000000 \n",
"2 0.005355 0.0 6.0 1000000000 \n",
"3 0.005354 0.0 6.0 1000000000 \n",
"4 0.005352 0.0 6.0 1000000000 \n",
"... ... ... ... ... \n",
"1199995 0.024075 0.0 27.0 500000000 \n",
"1199996 0.024076 0.0 27.0 500000000 \n",
"1199997 0.024076 0.0 27.0 500000000 \n",
"1199998 0.024411 0.0 27.0 500000000 \n",
"1199999 0.024075 0.0 27.0 500000000 \n",
"\n",
"[1200000 rows x 13 columns]"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dfL"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th>Ti</th>\n",
" <th>Iters</th>\n",
" <th>To</th>\n",
" <th>Iters2</th>\n",
" </tr>\n",
" <tr>\n",
" <th>Tt</th>\n",
" <th>Dist</th>\n",
" <th>%Async</th>\n",
" <th>NP</th>\n",
" <th>NS</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <td rowspan=\"5\" valign=\"top\">0.0</td>\n",
" <td rowspan=\"5\" valign=\"top\">1</td>\n",
" <td rowspan=\"5\" valign=\"top\">0.0</td>\n",
" <td rowspan=\"4\" valign=\"top\">2</td>\n",
" <td>4</td>\n",
" <td>0.099861</td>\n",
" <td>500.0</td>\n",
" <td>112.000000</td>\n",
" <td>500.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>8</td>\n",
" <td>0.099849</td>\n",
" <td>500.0</td>\n",
" <td>112.000000</td>\n",
" <td>500.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>16</td>\n",
" <td>0.099860</td>\n",
" <td>500.0</td>\n",
" <td>112.000000</td>\n",
" <td>500.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>32</td>\n",
" <td>0.099853</td>\n",
" <td>500.0</td>\n",
" <td>112.000000</td>\n",
" <td>500.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>4</td>\n",
" <td>2</td>\n",
" <td>0.049642</td>\n",
" <td>500.0</td>\n",
" <td>55.666667</td>\n",
" <td>500.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <td rowspan=\"5\" valign=\"top\">1.0</td>\n",
" <td rowspan=\"5\" valign=\"top\">2</td>\n",
" <td rowspan=\"5\" valign=\"top\">100.0</td>\n",
" <td>16</td>\n",
" <td>32</td>\n",
" <td>0.034899</td>\n",
" <td>21.0</td>\n",
" <td>14.000000</td>\n",
" <td>21.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td rowspan=\"4\" valign=\"top\">32</td>\n",
" <td>2</td>\n",
" <td>0.012084</td>\n",
" <td>101.0</td>\n",
" <td>7.000000</td>\n",
" <td>101.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>4</td>\n",
" <td>0.015599</td>\n",
" <td>54.0</td>\n",
" <td>7.000000</td>\n",
" <td>54.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>8</td>\n",
" <td>0.012685</td>\n",
" <td>54.0</td>\n",
" <td>7.000000</td>\n",
" <td>54.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>16</td>\n",
" <td>0.015421</td>\n",
" <td>44.0</td>\n",
" <td>7.000000</td>\n",
" <td>44.0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>360 rows × 4 columns</p>\n",
"</div>"
],
"text/plain": [
" Ti Iters To Iters2\n",
"Tt Dist %Async NP NS \n",
"0.0 1 0.0 2 4 0.099861 500.0 112.000000 500.0\n",
" 8 0.099849 500.0 112.000000 500.0\n",
" 16 0.099860 500.0 112.000000 500.0\n",
" 32 0.099853 500.0 112.000000 500.0\n",
" 4 2 0.049642 500.0 55.666667 500.0\n",
"... ... ... ... ...\n",
"1.0 2 100.0 16 32 0.034899 21.0 14.000000 21.0\n",
" 32 2 0.012084 101.0 7.000000 101.0\n",
" 4 0.015599 54.0 7.000000 54.0\n",
" 8 0.012685 54.0 7.000000 54.0\n",
" 16 0.015421 44.0 7.000000 44.0\n",
"\n",
"[360 rows x 4 columns]"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"grouped_aggL"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"TIEMPO EJECUCCION\n",
"Distribución BestFit -------------------------\n",
"Para 2 padres\n",
"EX numC= 4 p = 0.039 Diff = 0.09 Asíncrono\n",
"Para 4 padres\n",
"Para 8 padres\n",
"EX numC= 4 p = 0.0 Diff = 1.411 Síncrono\n",
"Para 16 padres\n",
"EX numC= 2 p = 0.014 Diff = 3.662 Asíncrono\n",
"Para 32 padres\n",
"EX numC= 4 p = 0.002 Diff = 4.589 Asíncrono\n",
"Distribución WorstFit -------------------------\n",
"Para 2 padres\n",
"Para 4 padres\n",
"EX numC= 16 p = 0.046 Diff = 0.304 Síncrono\n",
"EX numC= 32 p = 0.012 Diff = 0.542 Síncrono\n",
"Para 8 padres\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:10: UserWarning: Boolean Series key will be reindexed to match DataFrame index.\n",
" # Remove the CWD from sys.path while we load stuff.\n",
"/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:11: UserWarning: Boolean Series key will be reindexed to match DataFrame index.\n",
" # This is added back by InteractiveShellApp.init_path()\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Para 16 padres\n",
"EX numC= 2 p = 0.023 Diff = 6.349 Asíncrono\n",
"EX numC= 4 p = 0.0 Diff = 1.799 Asíncrono\n",
"EX numC= 8 p = 0.046 Diff = 0.261 Asíncrono\n",
"Para 32 padres\n",
"EX numC= 2 p = 0.01 Diff = 18.514 Asíncrono\n",
"EX numC= 4 p = 0.0 Diff = 4.454 Asíncrono\n",
"EX numC= 8 p = 0.003 Diff = 1.92 Asíncrono\n"
]
}
],
"source": [
"print(\"TIEMPO EJECUCCION\")\n",
"for dist in [1,2]:\n",
" print(\"Distribución \" + dist_names[dist] + \" -------------------------\")\n",
" dist_v = str(dist)+\",\"+str(dist)\n",
" for numP in values:\n",
" print(\"Para \", numP, \" padres\")\n",
" for numC in values:\n",
" if numP != numC:\n",
" group = str(numP) + \",\" + str(numC)\n",
" v1 = dfG[(dfG[\"%Async\"] == 0.0)][(dfG.Groups == group)][(dfG[\"Dist\"] == dist_v)]['TE']\n",
" v2 = dfG[(dfG[\"%Async\"] == 100.0)][(dfG.Groups == group)][(dfG[\"Dist\"] == dist_v)]['TE']\n",
" res = stats.ttest_ind(v1, v2)\n",
" diff = grouped_aggG['TE'].loc[(dist_v, 0.0, group)] - grouped_aggG['TE'].loc[(dist_v, 100.0, group)]\n",
" if diff > 0:\n",
" mejor = \"Asíncrono\"\n",
" else:\n",
" mejor = \"Síncrono\"\n",
" \n",
" if res[1] < p_value:\n",
" print(\"EX numC=\", numC, \"p =\", round(res[1],3), \"Diff =\", abs(round(diff,3)), mejor)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"TIEMPO MALLEABILITY\n",
"Distribución 1 -------------------------\n",
"Para 2 padres\n",
"TR numC= 8 p = 0.0 Diff = 0.029 Síncrono\n",
"Para 4 padres\n",
"TR numC= 8 p = 0.006 Diff = 0.043 Síncrono\n",
"TR numC= 32 p = 0.025 Diff = 0.052 Síncrono\n",
"Para 8 padres\n",
"TR numC= 4 p = 0.016 Diff = 0.022 Asíncrono\n",
"TR numC= 16 p = 0.009 Diff = 0.031 Síncrono\n",
"TR numC= 32 p = 0.0 Diff = 0.483 Síncrono\n",
"Para 16 padres\n",
"TR numC= 8 p = 0.002 Diff = 0.072 Síncrono\n",
"TR numC= 32 p = 0.019 Diff = 0.549 Síncrono\n",
"Para 32 padres\n",
"TR numC= 4 p = 0.002 Diff = 0.981 Síncrono\n",
"TR numC= 8 p = 0.001 Diff = 0.972 Síncrono\n",
"TR numC= 16 p = 0.001 Diff = 0.831 Síncrono\n",
"Distribución 2 -------------------------\n",
"Para 2 padres\n",
"TR numC= 4 p = 0.0 Diff = 0.069 Síncrono\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:9: UserWarning: Boolean Series key will be reindexed to match DataFrame index.\n",
" if __name__ == '__main__':\n",
"/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:10: UserWarning: Boolean Series key will be reindexed to match DataFrame index.\n",
" # Remove the CWD from sys.path while we load stuff.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"TR numC= 8 p = 0.029 Diff = 0.035 Síncrono\n",
"Para 4 padres\n",
"TR numC= 8 p = 0.02 Diff = 0.031 Síncrono\n",
"TR numC= 32 p = 0.009 Diff = 0.601 Síncrono\n",
"Para 8 padres\n",
"TR numC= 16 p = 0.011 Diff = 0.729 Síncrono\n",
"TR numC= 32 p = 0.018 Diff = 0.423 Síncrono\n",
"Para 16 padres\n",
"TR numC= 4 p = 0.001 Diff = 0.884 Síncrono\n",
"TR numC= 32 p = 0.021 Diff = 0.36 Síncrono\n",
"Para 32 padres\n",
"TR numC= 2 p = 0.029 Diff = 1.548 Síncrono\n",
"TR numC= 4 p = 0.008 Diff = 0.84 Síncrono\n",
"TR numC= 8 p = 0.011 Diff = 0.726 Síncrono\n",
"TR numC= 16 p = 0.004 Diff = 0.606 Síncrono\n"
]
}
],
"source": [
"print(\"TIEMPO MALLEABILITY\")\n",
"for dist in [1,2]:\n",
" print(\"Distribución \" + dist_names[dist] + \" -------------------------\")\n",
" dist_v = str(dist)+\",\"+str(dist)\n",
" for numP in values:\n",
" print(\"Para \", numP, \" padres\")\n",
" for numC in values:\n",
" if numP != numC:\n",
" v1 = dfM[(dfM[\"%Async\"] == 0.0)][(dfM.NP == numP)][(dfM.NS == numC)][(dfM[\"Dist\"] == dist_v)]['TS']\n",
" v2 = dfM[(dfM[\"%Async\"] == 100.0)][(dfM.NP == numP)][(dfM.NS == numC)][(dfM[\"Dist\"] == dist_v)]['TA']\n",
" res = stats.ttest_ind(v1, v2)\n",
" diff = grouped_aggM['TS'].loc[(dist_v, 0.0, numP, numC)] - grouped_aggM['TA'].loc[(dist_v, 100.0, numP, numC)]\n",
" if diff > 0:\n",
" mejor = \"Asíncrono\"\n",
" else:\n",
" mejor = \"Síncrono\"\n",
" if res[1] < p_value:\n",
" print(\"TR numC=\", numC, \"p =\", round(res[1],3), \"Diff =\", abs(round(diff,3)), mejor)"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"TIEMPO Iters\n",
"Distribución BestFit -------------------------\n",
"Para 2 padres\n",
"Ti numC= 4 p = 0.035 Diff = 0.0001 Síncrono\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:12: UserWarning: Boolean Series key will be reindexed to match DataFrame index.\n",
" if sys.path[0] == '':\n",
"/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:13: UserWarning: Boolean Series key will be reindexed to match DataFrame index.\n",
" del sys.path[0]\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Ti numC= 8 p = 0.025 Diff = 0.0001 Síncrono\n",
"Ti numC= 16 p = 0.002 Diff = 0.0001 Síncrono\n",
"Ti numC= 32 p = 0.007 Diff = 0.0001 Síncrono\n",
"Para 4 padres\n",
"Ti numC= 16 p = 0.0 Diff = 0.0006 Síncrono\n",
"Para 8 padres\n",
"Ti numC= 4 p = 0.0 Diff = 0.0009 Síncrono\n",
"Ti numC= 32 p = 0.007 Diff = 0.0029 Síncrono\n",
"Para 16 padres\n",
"Ti numC= 32 p = 0.0 Diff = 0.0097 Síncrono\n",
"Para 32 padres\n",
"Ti numC= 2 p = 0.0 Diff = 0.0029 Síncrono\n",
"Ti numC= 4 p = 0.0 Diff = 0.0056 Síncrono\n",
"Ti numC= 8 p = 0.0 Diff = 0.0055 Síncrono\n",
"Ti numC= 16 p = 0.0 Diff = 0.0053 Síncrono\n",
"Distribución WorstFit -------------------------\n",
"Para 2 padres\n",
"Ti numC= 32 p = 0.024 Diff = 0.0256 Síncrono\n",
"Para 4 padres\n",
"Ti numC= 16 p = 0.0 Diff = 0.0243 Síncrono\n",
"Ti numC= 32 p = 0.0 Diff = 0.0343 Síncrono\n",
"Para 8 padres\n",
"Ti numC= 4 p = 0.036 Diff = 0.0044 Síncrono\n",
"Ti numC= 16 p = 0.0 Diff = 0.008 Síncrono\n",
"Ti numC= 32 p = 0.0 Diff = 0.0225 Síncrono\n",
"Para 16 padres\n",
"Ti numC= 2 p = 0.0 Diff = 0.0067 Síncrono\n",
"Ti numC= 4 p = 0.0 Diff = 0.0092 Síncrono\n",
"Ti numC= 8 p = 0.0 Diff = 0.0098 Síncrono\n",
"Ti numC= 32 p = 0.0 Diff = 0.0224 Síncrono\n",
"Para 32 padres\n",
"Ti numC= 2 p = 0.0 Diff = 0.0058 Síncrono\n",
"Ti numC= 4 p = 0.0 Diff = 0.0094 Síncrono\n",
"Ti numC= 8 p = 0.0 Diff = 0.0064 Síncrono\n",
"Ti numC= 16 p = 0.0 Diff = 0.0092 Síncrono\n"
]
}
],
"source": [
"print(\"TIEMPO Iters\")\n",
"for dist in [1,2]:\n",
" print(\"Distribución \" + dist_names[dist] + \" -------------------------\")\n",
" dist_v = str(dist)+\",\"+str(dist)\n",
" for numP in values:\n",
" print(\"Para \", numP, \" padres\")\n",
" for numC in values:\n",
" if numP != numC:\n",
" #exp = dfL[(dfL[\"Tt\"] == 0)][(dfL[\"Dist\"] == 1)][(dfL[\"%Async\"] == 0.0)][(dfL.NP == numP)][(dfL.NS == numC)]\n",
" #TimeOp = exp['Ti'] \n",
" #print(TimeOp)\n",
" v1 = dfL[(dfL[\"Tt\"] == 0)][(dfL[\"Dist\"] == dist)][(dfL[\"%Async\"] == 100.0)][(dfL.NP == numP)][(dfL.NS == numC)]['Ti']\n",
" v2 = dfL[(dfL[\"Tt\"] == 1)][(dfL[\"Dist\"] == dist)][(dfL[\"%Async\"] == 100.0)][(dfL.NP == numP)][(dfL.NS == numC)]['Ti']\n",
" res = stats.ttest_ind(v1, v2, equal_var = False)\n",
" diff = grouped_aggL['Ti'].loc[(0, dist, 0.0, numP, numC)] - grouped_aggL['Ti'].loc[(1, dist, 100.0, numP, numC)]\n",
" if diff > 0:\n",
" mejor = \"Asíncrono\"\n",
" else:\n",
" mejor = \"Síncrono\"\n",
" if res[1] < p_value:\n",
" #and abs(diff) > grouped_aggL['Ti'].loc[(0, dist, 0.0, numP, numC)]\n",
" print(\"Ti numC=\", numC, \"p =\", round(res[1],3), \"Diff =\", abs(round(diff,4)), mejor)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Distribución BestFit -------------------------\n",
"Para 2 padres\n",
"NC=4 Es mejor Asíncrono con una diff de 0.123\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:10: UserWarning: Boolean Series key will be reindexed to match DataFrame index.\n",
" # Remove the CWD from sys.path while we load stuff.\n",
"/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:14: UserWarning: Boolean Series key will be reindexed to match DataFrame index.\n",
" \n",
"/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:16: UserWarning: Boolean Series key will be reindexed to match DataFrame index.\n",
" app.launch_new_instance()\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"NC=8 Es mejor Asíncrono con una diff de 0.07\n",
"NC=16 Es mejor Asíncrono con una diff de 0.046\n",
"NC=32 Es mejor Asíncrono con una diff de 0.014\n",
"Para 4 padres\n",
"NC=2 Es mejor Asíncrono con una diff de 0.645\n",
"NC=8 Es mejor Asíncrono con una diff de 0.13\n",
"NC=16 Es mejor Asíncrono con una diff de 0.052\n",
"NC=32 Es mejor Síncrono con una diff de 0.005\n",
"Para 8 padres\n",
"NC=2 Es mejor Asíncrono con una diff de 1.523\n",
"NC=4 Es mejor Asíncrono con una diff de 0.354\n",
"NC=16 Es mejor Asíncrono con una diff de 0.066\n",
"NC=32 Es mejor Síncrono con una diff de 0.327\n",
"Para 16 padres\n",
"NC=2 Es mejor Asíncrono con una diff de 3.676\n",
"NC=4 Es mejor Asíncrono con una diff de 1.235\n",
"NC=8 Es mejor Asíncrono con una diff de 0.406\n",
"NC=32 Es mejor Síncrono con una diff de 0.304\n",
"Para 32 padres\n",
"NC=2 Es mejor Asíncrono con una diff de 16.171\n",
"NC=4 Es mejor Asíncrono con una diff de 4.551\n",
"NC=8 Es mejor Asíncrono con una diff de 1.599\n",
"NC=16 Es mejor Asíncrono con una diff de 0.12\n",
"Distribución WorstFit -------------------------\n",
"Para 2 padres\n",
"NC=4 Es mejor Asíncrono con una diff de 0.13\n",
"NC=8 Es mejor Asíncrono con una diff de 0.064\n",
"NC=16 Es mejor Asíncrono con una diff de 0.034\n",
"NC=32 Es mejor Síncrono con una diff de 0.151\n",
"Para 4 padres\n",
"NC=2 Es mejor Asíncrono con una diff de 0.637\n",
"NC=8 Es mejor Asíncrono con una diff de 0.142\n",
"NC=16 Es mejor Síncrono con una diff de 0.28\n",
"NC=32 Es mejor Síncrono con una diff de 0.499\n",
"Para 8 padres\n",
"NC=2 Es mejor Asíncrono con una diff de 1.575\n",
"NC=4 Es mejor Asíncrono con una diff de 0.344\n",
"NC=16 Es mejor Síncrono con una diff de 0.32\n",
"NC=32 Es mejor Síncrono con una diff de 0.278\n",
"Para 16 padres\n",
"NC=2 Es mejor Asíncrono con una diff de 6.404\n",
"NC=4 Es mejor Asíncrono con una diff de 1.803\n",
"NC=8 Es mejor Asíncrono con una diff de 0.308\n",
"NC=32 Es mejor Síncrono con una diff de 0.113\n",
"Para 32 padres\n",
"NC=2 Es mejor Asíncrono con una diff de 18.438\n",
"NC=4 Es mejor Asíncrono con una diff de 4.533\n",
"NC=8 Es mejor Asíncrono con una diff de 1.927\n",
"NC=16 Es mejor Asíncrono con una diff de 0.481\n"
]
}
],
"source": [
"iters = dfM['Iters'].mean()\n",
"resultados = [0,0]\n",
"for dist in [1,2]:\n",
" print(\"Distribución \" + dist_names[dist] + \" -------------------------\")\n",
" dist_v = str(dist)+\",\"+str(dist)\n",
" for numP in values:\n",
" print(\"Para \", numP, \" padres\")\n",
" for numC in values:\n",
" if numP != numC:\n",
" Titer = dfL[(dfL[\"Tt\"] == 0)][(dfL[\"Dist\"] == dist)][(dfL.NP == numC)]['Ti'].mean() #Tiempo por iteracion\n",
" i=0\n",
" for adr in [0.0, 100.0]:\n",
" \n",
" auxExp = dfM[(dfM[\"Dist\"] == dist_v)][(dfM[\"%Async\"] == adr)][(dfM.NP == numP)][(dfM.NS == numC)]\n",
" Tr = auxExp['TS'].mean() + auxExp['TA'].mean() #Tiempo de redistribucion\n",
" M_it = dfL[(dfL[\"Tt\"] == 1)][(dfL[\"Dist\"] == dist)][(dfL[\"%Async\"] == adr)][(dfL.NP == numP)][(dfL.NS == numC)]['Ti'].count()/3 #Iteraciones asincronas\n",
" #No se presupone una diferencia temporal entre iteraciones sincronas y asincronas\n",
" if(M_it > iters):\n",
" M_it = iters\n",
" resultados[i] = (iters - M_it) * Titer + Tr\n",
" i+=1\n",
" #print(M_it)\n",
" #print(Titer)\n",
" #print((iters - M_it) * Titer)\n",
" #print(Tr)\n",
" #print(\"End\")\n",
" \n",
" if resultados[0] > resultados[1]:\n",
" mejor = \"Asíncrono\"\n",
" else:\n",
" mejor = \"Síncrono\"\n",
" diff = abs(round(resultados[0] - resultados[1], 3))\n",
" print(\"NC=\"+ str(numC) + \" Es mejor \" + mejor + \" con una diff de \"+ str(diff))\n",
" #TODO Comprobar"
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th>Ti</th>\n",
" <th>Iters</th>\n",
" <th>To</th>\n",
" <th>Iters2</th>\n",
" </tr>\n",
" <tr>\n",
" <th>Tt</th>\n",
" <th>Dist</th>\n",
" <th>%Async</th>\n",
" <th>NP</th>\n",
" <th>NS</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <td rowspan=\"5\" valign=\"top\">0.0</td>\n",
" <td rowspan=\"5\" valign=\"top\">1</td>\n",
" <td rowspan=\"5\" valign=\"top\">0.0</td>\n",
" <td rowspan=\"4\" valign=\"top\">2</td>\n",
" <td>4</td>\n",
" <td>0.099861</td>\n",
" <td>500.0</td>\n",
" <td>112.000000</td>\n",
" <td>500.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>8</td>\n",
" <td>0.099849</td>\n",
" <td>500.0</td>\n",
" <td>112.000000</td>\n",
" <td>500.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>16</td>\n",
" <td>0.099860</td>\n",
" <td>500.0</td>\n",
" <td>112.000000</td>\n",
" <td>500.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>32</td>\n",
" <td>0.099853</td>\n",
" <td>500.0</td>\n",
" <td>112.000000</td>\n",
" <td>500.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>4</td>\n",
" <td>2</td>\n",
" <td>0.049642</td>\n",
" <td>500.0</td>\n",
" <td>55.666667</td>\n",
" <td>500.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <td rowspan=\"5\" valign=\"top\">1.0</td>\n",
" <td rowspan=\"5\" valign=\"top\">2</td>\n",
" <td rowspan=\"5\" valign=\"top\">100.0</td>\n",
" <td>16</td>\n",
" <td>32</td>\n",
" <td>0.034899</td>\n",
" <td>21.0</td>\n",
" <td>14.000000</td>\n",
" <td>21.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td rowspan=\"4\" valign=\"top\">32</td>\n",
" <td>2</td>\n",
" <td>0.012084</td>\n",
" <td>101.0</td>\n",
" <td>7.000000</td>\n",
" <td>101.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>4</td>\n",
" <td>0.015599</td>\n",
" <td>54.0</td>\n",
" <td>7.000000</td>\n",
" <td>54.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>8</td>\n",
" <td>0.012685</td>\n",
" <td>54.0</td>\n",
" <td>7.000000</td>\n",
" <td>54.0</td>\n",
" </tr>\n",
" <tr>\n",
" <td>16</td>\n",
" <td>0.015421</td>\n",
" <td>44.0</td>\n",
" <td>7.000000</td>\n",
" <td>44.0</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>360 rows × 4 columns</p>\n",
"</div>"
],
"text/plain": [
" Ti Iters To Iters2\n",
"Tt Dist %Async NP NS \n",
"0.0 1 0.0 2 4 0.099861 500.0 112.000000 500.0\n",
" 8 0.099849 500.0 112.000000 500.0\n",
" 16 0.099860 500.0 112.000000 500.0\n",
" 32 0.099853 500.0 112.000000 500.0\n",
" 4 2 0.049642 500.0 55.666667 500.0\n",
"... ... ... ... ...\n",
"1.0 2 100.0 16 32 0.034899 21.0 14.000000 21.0\n",
" 32 2 0.012084 101.0 7.000000 101.0\n",
" 4 0.015599 54.0 7.000000 54.0\n",
" 8 0.012685 54.0 7.000000 54.0\n",
" 16 0.015421 44.0 7.000000 44.0\n",
"\n",
"[360 rows x 4 columns]"
]
},
"execution_count": 48,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"grouped_aggL"
]
},
{
"cell_type": "code",
"execution_count": 88,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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kO539wOHdswsmcjkJhERERGR9y2WQPqKmX0TSDezjwJPA3+Ib1kiIiIi0lvEEkhvBhqApcD/A14AvhXPokRERESk94hl2aeDZvY88Ly7N3RBTSIiIiLSi3Q4QmoRd5jZNmAFUG1mDWb27a4rT0RERER6us5u2V9PZHZ9ubvnu3secBpwupl9tUuqExEREZEer7NA+q/AdHdfd+iAu68FLo++JiIiIiLyvnUWSMPuvu3Ig9HnSMPxK0lEREREepPOAumB9/iaiIiIiEjMOptlf4qZ7W7nuAFpcapHRERERHqZDgOpu4e6shARERER6Z1iWRhfRERERCRuFEhFREREJFBxDaRmdp6ZVZvZajO7uZPzPmNmbmYT41mPiIiIiCSeuAVSMwsBPwHOB8qA6WZW1s55WcBXgLnxqkVEREREElc8R0grgNXuvtbdDwAzgantnPcdYAbwdhxrEREREZEEFc9AOgjY2Ga/NnrsMDMbBwxx9z/GsQ4RERERSWDxDKTWzjE//KJZEvBD4MajXsjsajObb2bzGxoajmOJIiIiIhK0eAbSWmBIm/3BwKY2+1nAScDLZrYemATMam9ik7s/7O4T3X1iYWFhHEsWERERka4Wz0A6Dyg1s2FmlgJMA2YdetHdd7l7gbuXuHsJ8BrwCXefH8eaRERERCTBxC2QunsLcC3wIvAm8JS7Lzezu8zsE/H6XBERERHpXjrrZf++ufsLwAtHHPt2B+d+JJ61iIiIiEhiUqcmEREREQmUAqmIiIiIBEqBVEREREQCpUAqIiIiIoFSIBURERGRQCmQioiIiEigFEhFREREJFAKpCIiIiISKAVSEREREQmUAqmIiIiIBEqBVEREREQCpUAqIiIiIoFSIBURERGRQCmQioiIiEigFEhFREREJFAKpCIiIiISKAVSEREREQmUAqmIiIiIBEqBVEREREQCpUAqIiIiIoFSIBURERGRQCmQioiIiEigFEhFREREJFAKpCIiIiISKAVSEREREQmUAqmIiIiIBEqBVEREREQCpUAqIiIiIoFSIBURERGRQCmQioiIiEigFEhFREREJFAKpCIiIiISKAVSEREREQmUAqmIiIiIBEqBVEREREQCpUAqIiIiIoFSIBURERGRQCmQioiIiEigFEhFREREJFAKpCIiIiISKAVSEREREQmUAqmIiIiIBEqBVEREREQCpUAqIiIiIoFSIBURERGRQCmQioiIiEigFEhFREREJFAKpCIiIiISKAVSEREREQmUAqmIiIiIBEqBVEREREQCpUAqIiIiIoFSIBURERGRQCmQioiIiEigFEhFREREJFBxDaRmdp6ZVZvZajO7uZ3XbzCzKjNbYmZ/M7Oh8axHRERERBJP3AKpmYWAnwDnA2XAdDMrO+K0RcBEdz8ZeAaYEa96RERERCQxxXOEtAJY7e5r3f0AMBOY2vYEd5/t7m9Fd18DBsexHhERERFJQPEMpIOAjW32a6PHOvIF4M/tvWBmV5vZfDOb39DQcBxLFBEREZGgxTOQWjvHvN0TzS4HJgL3tPe6uz/s7hPdfWJhYeFxLFFEREREgpYcx2vXAkPa7A8GNh15kpmdDdwKfNjd98exHhERERFJQPEcIZ0HlJrZMDNLAaYBs9qeYGbjgJ8Bn3D3+jjWIiIiIiIJKm6B1N1bgGuBF4E3gafcfbmZ3WVmn4iedg+QCTxtZm+Y2awOLiciIiIiPVQ8b9nj7i8ALxxx7Nttts+O5+eLiIiISOJTpyYRERERCZQCqYiIiIgESoFURERERAKlQCoiIiIigVIgFREREZFAKZCKiIiISKAUSEVEREQkUAqkIiIiIhIoBVIRERERCZQCqYiIiIgESoFURERERAKlQCoiIiIigVIgFREREZFAKZCKiIiISKAUSEVEREQkUAqkIiIiIhIoBVIRERERCZQCqYiIiIgESoFURERERAKlQCoiIiIigVIgFREREZFAKZCKiIiISKAUSEVEREQkUAqkIiIiIhIoBVIRERERCZQCqYiIiIgESoFURERERAKlQCoiIiIigVIgFREREZFAKZCKiIiISKAUSEVEREQkUAqkIiIiIhIoBVIRERERCZQCqYiIiIgESoFURERERAKlQCoiIiIigVIgFREREZFAKZCKiIiISKAUSEVEREQkUAqkIiIiIhIoBVIRERERCZQCqYiIiIgESoFURERERAKlQCoiIiIigVIgFREREZFAKZCKiIiISKAUSEVEREQkUAqkIiIiIhIoBVIRERERCZQCqYiIiIgESoFURERERAKlQCoiIiIigVIgFREREZFAKZCKiIiISKDiGkjN7Dwzqzaz1WZ2czuvp5rZk9HX55pZSTzrEREREZHEE7dAamYh4CfA+UAZMN3Myo447QtA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\n",
"text/plain": [
"<Figure size 720x504 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
},
{
"data": {
"image/png": "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MCIiIr2u23d3dy/t0aNH7uLFi126du1aCADjxo1rl5WVZb1ixYpDVX3O2MRv+nrJkiXOv/76q/O2bdvSnJ2dy+Li4oILCgoM5vKbcnJyKjP+X0qJBQsWHIyKiioyzXPLLbcU9uzZ8/IPP/zg0r9//6Dp06cfGThwYLVdFlhDSkRERFRHxmA0LGwefH3/fbVP6ZUrm2oda508edI6KyvLCgDy8vLE2rVrW4aGhhYCwHvvvee+Zs0al0WLFmVaWVlVuY4NGza0PHPmjFVeXp74+eefW/Xq1SsvOzvbysXFpdTZ2bls586d9qmpqS0A4Lbbbru8adMm59OnT1sVFRWJH374ocoq3sTExJx3333Xs6xMxai//fabAwDs37/fNjQ0tGjChAln+/btm71r1y6Hmuwra0iJiIiI6sA0GHV1TQRQPtBp9+4h9llZns61Gdh0/Phxm9GjR/uWlpZCSikGDRp04b777rsEAC+++GKndu3aFcXGxoYCQFJS0sUpU6acqryO2NjYvOHDh/seOXLEfujQoedvu+22/IKCgoIZM2Z4BAUFhfn7+xdGRUVdBoBOnToVv/TSSycTEhJCPTw8ijt37pxfWlpqtsr07bffPvnYY491DAkJCZNSCm9v76KUlJSDX3/9tdv8+fNbW1tbSw8Pj+K33nrrZE32lQEpERERUS2ZC0aNXF0T4ez8n8K0tFF+tZn6KT4+vuCPP/7Yby6tpKRke03W4e7uXjJr1qxjpu85ODjIdevWZZjL/+yzz55/9tlnz1d+f+HChUdMXzs5Oclvv/32aOV8b7311um33nrrdE3KZopN9kRERES1lJu71WwwamRrm1AWEjIrMydns2MjF61ZYQ0pERERUS117PhitXnc3ZNzazsXaV2MHTv2PIBrajubItaQEhEREZGuGJASERERmSGhpjei+lNWViYAlFV+nwEpERERkRnn8stw/vx5BqX1pKysTJw7d84FwN7KaexDSkRERGTG0kNFiOmUi3PnztV6HadPn7YuLS11rz7nTaEMwN6SkpJHKicwICUiIiIyI78E8PX1rdM6wsLC9kgpY+upSDcsNtkTERERka4YkBIRERGRrhiQEhEREZGuGJASERERka4YkBIRERGRrhiQEhEREZGuGJASERERka4YkBIRERGRrhiQEhEREZGuGJASERERka4YkBIRERGRrhiQEhEREZGuGJASERERka4YkBIRERGRrhiQEhEREZGuGJASERERka4YkBIRERGRrhiQEhEREZGuGJASERERka4YkBIRERGRrhiQEhEREZGuGJASERERka4YkBIRERGRrhiQEhEREZGuGJASERERka4YkBIRERGRrhiQEhEREZGuGJASERERka4YkBIRERGRrhiQEhEREZGuGJASERERka4YkBIRERGRrhiQEhEREZGuGJASERERka4YkBIRERGRrhiQEhEREZGuGJA2J5MnAykplvOkpKh8N7rpdwLL37OcZ/l7Kh8RERE1aQ0WkAohZgohzgoh9laRLoQQ04QQB4UQu4UQtzRUWW4YXbsCw4ZVHZSmpKj0rl0bt1x68OsDHBtfdVC6/D2V7tencctFRERE160ha0i/BNDPQnp/AIHa8hiAjxqwLDeGxERg3jzzQakxGJ03T+W70fV7Aeg4xXxQagxGO05R+YiIiKhJa7CAVEq5DsAFC1kGAZgllU0AWgkh2jVUeW4YpkFpdrZ672YLRo1Mg9ILJ9R7DEaJiIiaHSGlbLiVC+EDYImUMsJM2hIAb0spN2ivVwN4SUq5zdI6nZ2dZUxMTAOUtmrHLx2Hs50zWtm3AgDsP5kDAAjzank1T3ZhNnKLctHBpUPjFCo7G/tP5QBCIOxMJmBnB1hZladbOq8NkVbX9RoACAAG7f/GRVR6bZBaPpPFGthf5gtIIMzuMHDKAFyxAQwGtQhR/v+aLHXJL4TlY0SN4/hxwNkZaFX1dxbZ2UBuLtChkb6zRNSsmL1v1MKvv/66XUoZWx9lupFZ67htc7/cZqMWIcRjUM36sLOza8gymeUsC7Dv7FGEt4m4GpSayi7Mxr6zexHesk3jFapVK+DsZaC0FHBwAFq0KE+zFBTVNq1yupDlC8oqvr66aO+j0uvKn4E0fzWYIwFILXo1/bdUqHVZAXB3BApaAKVlKtgtKytfSkoqvjZd6ktDBrw1zX+zc3YG9u0DwsOvBqUVZGeXpxMRke70DEhPADCtmvAGcNJcRinlDAAzACA2NlauXbu2wQtXwZkUpCy/C8NOH8H7w37AxyvtAQBzH++GlMMpGDZvMFa2bYHEft8Ano3UZJ6SguFf7gC8vDD3s7GWm+vLSoHSy0BxrlpK8oCSXPOvS/K0f3OBYtP3TdMuo4q/Ha5l5QjYOAHWzoCNM2Bd6f82zlW8Ns1nfO0EGGyu3cby9zB80xWgrCXm+j8N2OQBbWKB7nMAh7Y1P6alpUBREVBYqJbG+n9REZCfX/7/+mBvrxY7O/3+b9B5Eg9jV5b338fwA+Xf2avvr1x5c3VxIaLrMvyTjQC0+0YdCFYS1IieAelPAJ4RQnwHIB7AJSnlKR3LUzXPRCT2W4R5y+/CsHmDEeHwBtrYxVwNRue1lUjst6j+gtGyEpPgzzRA1F7v2wZ8/SnuuX0QHBy2AjPigKX9gAvRQEvba4PJ0vyab9u6xbWBoEM7wCbo+oNJayfAYFX9NuvC2GfUbi7g5g14vgucHAec/Q1YfgvQfS7QpmfN1mVlBTg6qkUvUgJXrjR8EJybC2Rlmc9TWFg/NcY2NvoGxT16AHPnquBz/CxVU3qz9rcmImriGiwgFULMAXA7AHchxAkA/wRgAwBSyo8B/AzgTgAHAeQDeKihylIvTILS/kf/DnfbBKyZt1MFo30XAK2igMtHK9Uqmgkmr6ltNFMTWVpYfXkGAUPkdyiEA4BWQFdPIGM7EBABeHQwU8toEjAa/1/5tXULQDSjqWlNBzBJb/VevxeA5QBOjAcMxcDqRKDL20DIuObRlC2ECqp06JpSQUlJ49QS5+RUnefKlbrvh8GgAuPUVMDOFnh+AjB+PODvr4L/5nBNEBHdBBosIJVS3ldNugTwdENtv0F4JuL2v/6Atp//BUeL1uJvLkCirQ2Q0rdmnxeGirWPxkDQyb1S7WMVweSuNODZl4EPPwVu74f7Pk8FIDB3pNaccLX25z2g+w1e+1N5NL3WtAKgPCg9Nh5wjQR2/h049xuQ8AVga6Y/IV3L2hpwclKLXsrKVGBaH8FvjhOQm6eC3IkT1dKuHdCtG5CQoJaYGH1rx4mIbmJ6Ntk3S2sLBLLLrGCLUnycAwwKH4K/tIsoDx4t9ZO0sq9bjcyuTcD0hSZNjZXWZZwSauvWG785MvMXwM/C1E7GoDTzF6D3QyooXR4D9FgAuEU3alGplgwGNWDPwaFu60lJAb7cAXTqBLi6qmC0pATYtEkt33+v8llZAVFRKjg1Bqr+/qxFJSJqBAxIr4Oxz+jstvZYVhiN6Rc2YNCWH7F4xONI9G2EAPDFF6vPk5h44wejAPDUz9Xn6fcCAC1gbR0HbBgGrOwGdP0f4DeGgcbNwNhqYOxDapzDd9484JlnVJ5z54DNm4GNG1WAOmsWMH26SmvdurwGNSEBiIsDWtZtChgiIrpWM+owqC/TAUw7xNs4Z/MOBvrciislhRg6dyBSDlfzjHnSl8etQP8daoDT5keAzWOAkusY7EXNj+kAJuPUT+aedubhASQlAZMmAatXqymhdu8GZswABg0CDh8GXnsNuOMOtZ6ICODRR4HPP1dTR9XnlGFERDcpBqQ1UHk0/b7SGAghMGPo93Cxd4F7WT6GzRvMoLSps28D3L4ciHgNyPxS1ZbmZOhdKmoIlkbTW3oEL6Ca7iMjKwadFy8CK1YAr78OdOwILFwIPPKICk5dXVWw+tprwNKlwPnzjbKLREQ3EgakNbA1/TuzUzt5OnlixsAvkFFchjvtCrA1/TsdS0k1YrACOv8buP1nIP+E6ld6bKHepaL6tnWr5amdTPtb10SrVkDfvsD//R/w888q6ExPB776ChgxQk2h9eabqqbV3R0ICgJGjVJN/zt2AMXF9bdvREQ3IPYhrYEXO/oD0ebnGR0cOhijokbhm92zsdHJVofSUa149Qf67wQ23ANsuBsIfh6Ifsf8xPvU/DR0f2shVNBpDDwBIC8P2L69fLDUypXA11+rNAcHoGvXiv1R27Wr3baJiG5ADEhrIszyj9sH/T7AmsNr8MCuVdgZXwAHmzqOCqbG0aIj0GcdsHM8kP4+cH4z0GMe4Nhe75JRc+TkBPTqpRZAzXN67Fj5YKlNm4D33y+vLe3YsWKAesst+s8/S0SkEwak9aCVfSt8MegL3PH1HfjH6n9gar+peheJasrKDoj9EHDvDmx5BFgWDXT/FmjbR++SUXMnhJpqqlMn4N571XuFhcCuXeUB6saNqusAANjaAtHRFYPUTp04GwQR3RQYkNaTPn598Le4v+GDzR9gYPBA9PbtrXeR6Hr43Au4RgHrhwJr+qp+puGvNK8nV1HTZ29fHmwanTyppp0yBqkzZgAffKDS2ratGKDGxgItWuhTdiKiBsSAtB693edtrDi0AqMXjcaeJ/fAxd5F7yLR9XAJBf66BdjyOLD7NeDc78CtXwN2rfUuGd3IvLyAwYPVAqgm/T17ygPUTZuARYtUmnEGAGOA2q0bEBjIWlQiavZY/VOPHG0c8fXgr3Ey9ySeXf6s3sWh2rBxAm6dDXSdDpxZDSy7Bcjaonep6GZiY6P6kz71lJqk/8ABNXn/0qXAK6+oeVO//RYYPRoIDlaT9995J/Dvf6uBVNnZeu8BEdF1Yw1pPYtrH4dXer6CiesmYlDwIAwOHax3keh6CQEEPgm4xapR+L/0AG55Hwh8ijVRpA93dxV03nmnel1aCqSlVaxFXb5cDaQCgNDQ8sefJiQAYWGqdpWIqIliQNoAJtw2AUszluLxJY+je8fuaNOijd5Fotpo3RXotwPYOArY9gxwbgMQ96mqRSXSk5UVEB6ulocfVu/l5ABbtpQHqD/+CMycqdKcnNRjT43N/PHxqqaViKiJYEDaAGytbDHrrlmImRGDxxY/hh+G/wDBmrXmyc4N6PUTsP9t1a/0YirQcwHgEqZ3yYgqatkS6NNHLYCqLT10qGIt6jvvqNpVAPD3rzhgKipKdRcgItIBA9IGEt4mHG/+5U2MWzkOX6V+hdFdRutdJKotYVAj7lsnAL/fByzvCsR/CviM0LtkRFUTAggIUMv996v38vMrTt6/Zg3wzTcqzd4eiImp2NTfnnPyElHjYEDagJ5LeA4/pf+EscvGItEnEZ1addK7SFQXbXurJvzf7gV+Hwmc+w245T01lylRc+DoCPTsqRZA1aKeOFFx8v5p04ApU1S6t3fFEf233KICVyKiesaAtAEZhAFf3vUlIj+KxOgfR2P1qNUwcF7L5s2xPfCXNUDqK8AfU4DzW4Ae8wEnH71LRnT9hAA6dFDLsGHqvaIiIDW1YlP/ggUqzcYG6NKlYlO/ry8H+xFRnTE6amA+rXzwQb8PsPbIWkzbPE3v4lB9MNgA0f8Ben4P5B4Alt8C/Pmz3qUiqh92dmoA1NixanqpzEzg1Ck1F+q4cWpi/s8/B0aOVP1Q27YFBg0C3noLSEkBcnP13gMiaoZYQ9oIHuryEBalLcLLv7yMvv59EebBATE3hA6DgVaRwPq7gV8HAOGvApH/AgycXoduMMagc9Ag9bqkBNi3r2JT/08/qTSDAYiIqFiLGhys3iciqgLvEI1ACIFPkz+Fs50zRv0wCsWlxXoXieqLcwDQdyPgNwbYNwlI6QsUntW7VEQNy9pajcp/4gngyy/VnKjnzwPLlgGvvaYC2LlzgTFj1ByorVsD/foBr7+u5ku9eFHvPSCiJoY1pI3E08kTHw/4GHfPvxuT1k/C67e/rneRqL5YOwAJnwMe3YFtTwPLooHuc4E2PfQuGVHjcXNTQWe/fup1WRmQnl6xL+rEiep9QNWamo7oDw9XgS4R3ZT47W9EQ8OG4oHOD+CNdW8gKSgJsV6xeheJ6pP/GMAtRjXhr74d6PIOEPICB3zQzclgUE+MCg0FHnpIvZebC2zbVt7Uv3SpqmEFVN/Url0rNvV7eupWfCJqXAxIG9m0/tOQciQFD/zwAHY8tgMONg56F4nqk2sU0G8bsHkMsHO8mhoq4QvA1kXvkhHpz9kZSExUC6CmnTp8uGIt6pQpqo8qoEbwmwaoXboAtrb6lZ+IGgwD0kbWyr4VZg6cib6z++KV1a/g/X7v610kqm+2LkCPBUDa+8CuF4HlserpTq5RepeMqGkRAvDzU8sI7UETBQXAjh3lAer69cCcOSrNzk7NhWra1O/tzVYIohsAA1Id3OF/B57p+gymbp6KgcEDkeibqHeRqL4JAYS+ALSOA34bBqxMAGKnA/4P6V0yoqbNwQHo3l0tRidOAJs3lzf1T58OvPeeSvPyunbyfkdHfcpORLXGgFQn79zxDlZmrsToH0dj9xO74WLPJt0bUpseQL+dwO8jVDP+uQ1A7H/VQCgiqhlvb7UMHapeX7kC7N5dsan/++9VmnEGANOmfn9/1qISNXGc9kknjjaOmHXXLJzIOYHnVjynd3GoITl4AokrgfAJQOZMYGU3IPeg3qUiar5sbYHYWOCZZ4DZs4GDB4EzZ9RcqC++CLi4AF99BTzwABAYCHh4AElJwKRJwOrVQE6O3ntARJWwhlRH8d7xeKXHK3hj/RsYFDwId4XcpXeRqKEYrICoiYB7N2Dj/cDyGCDhSzW5PhHVXZs2QHKyWgCgtBTYv1/VnpqO6gdUbWl4eMWm/pAQTt5PpCMGpDp7rddrWJqxFI8tfgy3drgVbVq00btI1JDa3wn036mmhlo/BAgdD0S9qR5HSkT1x8oKiIxUy6OPqveys4EtW8qb+RcuBD77TKW1bAnEx5cHqfHxakJ/ImoUDEh1Zmtli68Hf42YGTF4bPFj+GH4DxDs63Rja9EJuGMDsOMF4I8pQNYmNZG+o5feJSO6sbVqBfTtqxZATTt14EDFvqiTJpVP3h8YWHFEf2QkJ+8naiD8ZjUB4W3CMan3JIxfNR6zUmfhwS4P6l0kamhWdkDX/6mnO21+FFgeDdw6B2jbW++SEd08hFBPjAoOBh7U7rt5ecD27eXN/MuXA7NmqTRHR9V31XTAVLt2+pWf6AbCgLSJeC7hOfx04CeMXT4Wt/vcjk6tOuldJGoMPiMA1y7A+qFAyh1A54lA2MuAYF82Il04OQG9eqkFULWoR49WrEV9/32guFild+pUMUCNjlbzpRLRdWFA2kRYGazw5aAv0fnjznjox4fwy6hfYGBQcnNwCQP+uhXY8iiQ+qp6ulO3rwE7N71LRkRCAD4+arn3XvVeYSGwc2d5gLpxIzB3rkqztVVBqWlTf8eOnHaKqBqMeJoQX1dfTP3rVKQcScGHmz/UuzjUmGycgFu/VXOUnl4FLL8FOL9N71IRkTn29irgfP55FYgePQr8+aeaC/XZZ1VQ+sknKoD18VGT9w8eDLzzDvDrr8Dly3rvAVGTwxrSJmZM9BgsSl+El1e/jL7+fRHqEap3kaixCAEEPQ24dQU23AOs6g7E4uBn+AAAIABJREFUTAUCnmDtClFTZww6B2tTuRUXA3v2VGzqX7RIpVlZAZ07V2zqDwzk95xuaqwhbWKEEPg0+VO0sGmBUYtGobi0WO8iUWNzjwP67wA8/wJsfQrY+ABQnKd3qYjoetjYqMeYPvWUGhR14ABw7hywZAnwj3+oKaVmz1aDqYKDAXd34M47gYkTgVWrgEuX9N4DokbFGtImqK1TW3yS9Anunn833lz/Jv55+z/1LhI1NrvWwO1LgH1vArv/D7i4E+ixAHBhjTlRs+XuDgwYoBZATd6fllZx8v7ly9VAKiGA0NCKk/eHhqraVaIbEAPSJmpo2FDc3/l+TFw3EQOCBiDWK1bvIlFjEwYgYgLgngD8NgJY0RWI+wzwuVfvkhFRfbCyUk+MCg8HHn5YvXfpErB1a3kz/48/AjNnqjRnZyAuruLk/R4e+pWfqB4xIG3CPuz/IVIOp2DUD6Ow/bHtcLBx0LtIpIe2fdTTnTYMA36/D8j6DYieouYyJaIbi4sL0KePWgBVW3rwYMW+qG+/rWpXAcDfv7wGNSFB9U214ZPfqPlhQNqEtbJvhS8GfYG+s/vi1TWv4r2/vqd3kUgvju2BPmuBXS8Dae8B57cAPeappz4R0Y1LCDXgKTAQeOAB9V5+vpq839jUv3o18M03Ks3evuLk/d26qQFXRE0cA9Im7g7/O/B016fx/qb3kRyUjETfRL2LRHox2AC3vAu43wpseghYdgtw6zeAVz+9S0ZEjcnREejZUy2AqkU9frxiLeq0acCUKSq9Q4eKI/pvuUUFrkRNCAPSZuCdPu9g5aGVGP3jaOx5cg9a2rXUu0ikp45DgVadgQ13A2vvVP1MI/4JGDjYgeimJISafL9jR2DYMPVeURGwa1fFIHX+fJVmYwN06VJx8n4fH047RbritE/NQAvbFpg1eBZO5JzAc8uf07s41BS0DAT6bgT8HgT2TgTW9gMKz+ldKiJqKuzs1KCnZ58F5swBDh8GTp1Sc6GOG6dqWT/7DBgxAvDzA9q2BQYNAt56C0hJAfI41Rw1LtaQNhMJ3gn4R49/YNL6SRgUPAiDQgbpXSTSm7UjED8T8OgBbH0aWBat+pV63Kp3yYioKTIGnYO034+SEmDv3oq1qD/9pNIMBiAysmJTf1CQep+oAfDKakYcbRzh7+qPRxc/irOXz5rNk3I4BZN/m9zIJSPdCAH4P6xqS63sgF96AWnvqz5lRESWWFurpvsnngC+/FLNiXr+PPDzz8CECYCnJ/Ddd8BDD6k5UFu3Bvr1A/71L2DFCuDiRb33gG4grCFtRrp5d8OU36cg90ounljyBKzleAiTPj8ph1MwbMEwzLt7no6lJF24RQP9tgObRgM7XgDO/QYkzARs2N+YiK6DmxvQv79aAKCsDEhPL69B3bhRBaTGP3pDQirWokZEcPJ+qhUGpM1Iom8iFg5biOQ5yfgh7QfEuUTAx0HdNEyDUY7Ev0nZtgJ6/gCkvaumh1q+Wz3dybWz3iUjoubKYFC1o6GhqqYUAHJzK07ev2SJqmEFgBYtKk7en5AAtGmjW/Gp+WBA2swk+iZi0b2L0P+b/th+6T9wtuqIZRnZGLVoFINR0h43OB5oHQdsGA6sTAC6fqQGPxER1QdnZ6B3b7UAqrb08OHyx59u2gT85z+qjyoA+PpWnLw/KgqwtdWv/NQkMSBthvr49cFXg77CyB9GYvWFR7H6W8DV3hWvrHkFfq5+8G3le/VfX1dfeLf0hrWBp/qm0uY29XSn3+5TzfjnNgCxHwJWnHuQiOqZEGqkvp8fMHKkeq+gANixo7yZ/9df1Wh/QM0AEBNTcfJ+b2/9yk9NAqOUZmpE5xH454ofcDB/AW7vdDv83fyReTETvx//HXP3zkWpLL2a19pgjY4uHcuDVGPA6qr+be3QukJfVLpBOLQFeq8Cdv8fsP8t4MJ2oMd8wNlf75IR0Y3OwQHo3l0t/8/efcdXXZ7/H3/dZ8/skEkmI0Q2hCEgRFnuDsW2atufWvXbYWttqbXW1WGlX/3W0dZabd0Dq7VqVXBERVAMggxZypARMsggAbJz//74JCfn5GQckZN5PR+P8yDnfD7ncMcB79z3dV93mwMHAnf0//nPcFfrCYQpKYHL/FOmGJ8hhgwJpANUwZ4C9te+Qa77/7Gl7CVumnuTb7m+sbmR/dX72VO5h92Vu9lTtYc9VcbXL2x/gbLjgf0qPTZPl2E1IyoDl9XVF9+iOBlMFpj4e6MV1JpL4bUpMPMRSJW2YUKIXpaaChdcYDwAGhpg06bApf7nnjOuWSzG0r7/Un9WljTvH8QkkA5AbRuYZkb9hmH2KVy98NKADU1Ws5Ws6CyyorM4gzOC3n+04WhgWK3cw+6q3XxW8Rkrd62ktqk24P4Ed0J7SI1qD6uZUUY5gFlOCOr/Us6BM9fDexfCu1+BMT+HCb83AqsQQvQFmw2mTjUeP/qR8VppKaxd277U//DDxkwqQFxc4CzqtGlGPasYFORvowHGfzf9/SuNesD8zHyWX7A85F32HpuHcQnjGJcwLuia1prSY6W+GVX/4Lp632qe3vI0LbrFd7/FZCE9Mj0grPrPsko5QD/iyYQF78FH18K2P8LhD2D2M+BM6uuRCSGEYdgwOPdc4wHQ3AyffBK41P/yy8Y1pYw2U/4hNSdHmvcPUBJIB5COrZ3u533ftS8aSruilCLBk0CCJ4EZqTOCrreVA3QMq3uq9vD89uc5fPxwwP1emzdgRtU/rEo5QB8wO2DaX40l/A+vNk53mvU0JMzr65EJIUQwsxnGjzceV15pvFZZCR9+2B5Qn30W/v5341pkpDFz2rbMP3260Vs1FMuWQV4e5Hfz92dBgdHyaunSL/d9iSASSAeQwqLCbsNmWygtLCoMW/sn/3KAztTU17SXAfiF1Z3lO1nx2YqgcoBET2JAV4C2sCrlAGGWeSlET4L3LoC3zoDxv4PcpaBkZkEI0c9FR8OiRcYDjOb9n34a2Lz/t781XgfjyFP/WdRx44wa1Y7y8mDJEli+vPNQWlDQfl2cdGENpEqpxcDdgBl4UGv9hw7X04BHgKjWe67XWr8SzjENZEtn9fwTWX5mfp/2IvXavYxPGM/4hOBm7FprSo6VdFq/+t6+93hqy1MB5QBWkzWgO4B/WM2KziLGGSPlAF9G1FhYVAhrr4CNvzROdzr1UbBF9/XIhBAidCYTjB5tPL7T2nP56FFYt649pL72Gjz6qHHN5TLCp39ITUw0Qujy5X6h069Nnn8Y7W4GVZywsAVSpZQZ+DOwADgAFCqlXtRab/W77UZgudb6r0qpXOAVICNcYxJ9SylFoieRRE8iM4fPDLre2NzIviP7AutXq4xfn9v2HOW15QH3e21eX0jdWm3HbU7hvzsrfN0BnFZpGdIjq9dYso+fAxt+Cq9ONlpDxU7t65EJIcSJ83hg3jzjAUbz/s8/D9zRf9dd0NhoXE9Pb9/Rf+utRvj82aMQFSVhtJeEc4Z0GvCZ1no3gFLqaeB8wD+QaqDtsO1IoCiM4xH9nNVsJTsmm+yYzvtktpUDdKxf3Vm+k13Hd9FMPec8dZfv/iRPUuAmK79Z1hRvipQDtFEKRv/QCKHvLYHXZ8GUe2DEldJiRQgxOCgFGRnG45vfNF6rq4MNG9oD6po18MwzxjWLxWhJ5fXAr2426lQljIZVOANpCrDf7/kBYHqHe24BViqlfgS4gfmdfZBS6krgSoC0tLSTPlAxMHRXDrDk/jXUtVRw/bkx7Kls77u6p2oPq/at6rQcID0qPehUq7avh2Q5QNwMWLwe1lwMhVcbS/jT/goWd1+PTAghTj6Hw5gRnem3YnfwYHvbqTIbVNfAVVdJGO0F4Qyknf1trjs8/ybwsNb6TqXUTOAxpdRYrf2SA6C1fgB4AGDq1KkdP0MIlFI4zbGcOnwmpw4/Neh6Q3ODUQ7QIazurtzNv7b+K6gcIMIe0WVYHdTlAI44mPcKfPI72HwLVK6HOc9BxOi+HpkQQoRfSgp87WvGxqmH10NaGjzwY5g/X0JpmIUzkB4Ahvs9TyV4Sf5yYDGA1vp9pZQDiANKwzguMQTZzDZGxIxgRMyITq9X11cHhtXW+tXth7fz6mevUtdUF3B/WzlAQClAa3Ad8OUAJjOMuwniZsKab8FrU2H6Q5C+pK9HJoQQ4ddWM9pWQ+q/0UlCadiEM5AWAiOVUpnAQeAbwLc63LMPOAN4WCk1BmNLWxlC9LIIewQTEicwIXFC0DWtNcVHizvdbPXu5+/yxKYn0H6T/23lAJ2F1azoLKId0QOjHCBpgbGEv/oi41G2Gib9Ecy2vh6ZEEKEh/8Gpp2tu+w77r6XUBoWYQukWusmpdQPgRUYLZ3+obX+RCl1G7BOa/0icB3wd6XUtRjL+d/VWsuSvOhXlFIkeZNI8iZ1Ww7QFlb9SwLWFa2jorYi4P4Ie0SXYTUjKgOHxRH0e/QZ93A44234eCnsuBvK1xq78N3De3yrEEIMKB130+9sP3xGQmn4hbUPaWtP0Vc6vHaT39dbgVnhHIMQ4dZTOcCRuiO+nqv+YXXb4W2dlgMke5PbQ2rrcaxtwTXZm9z75QBmG0z5E8TPgg8ug9cmwcwnIHlR745DCCHCqbCw+7DZFkoLCyWQhoGc1CREmEU6IpmYOJGJiRODrrXoFqMcoJPNVu/sfYcnqoPLATKiMjoNq5nRmeEtB0i7EKLGw6oL4O0zYexNMPbXRs2pEEIMdKEcB5qfL2E0TCSQCtGHTMpEsjeZZG8ys9KCFwvqm+qDDgto+7qzcoBIe2RgSPXrvXpSygEiRsOitVD4P7DlVjj8Ppz6ODjiv9znCiGEGNIkkArRj9ktdkbGjmRk7MhOr7eVA3QMq1vLtvLfnf+lvrk+4P5kb3KnYTUrOotkbzKmUM6yt7hgxsMQPxvW/QhemwyzlkN88OlbQgghRCi6DKRKqRqC+4b6aK0juromhOgdoZYD+JcC7KnaQ8HeAh6vfjygHMBmtpEemR50qlXb19FOvzPulYIR34OYKcYS/hunwaT/hdHXyOlOQgghvrAuA6nW2gvQuiu+GHgMo9n9xYC3V0YnhDhhoZYDdAyreyr38OHBD6msqwy4P9Ie2T6jGuUXVqc9Tfq2W3Gs/wkcXg3THwSr/LwqhBAidKEs2S/SWvsf+flXpdRaYFmYxiSE6AU9lQNU1VUFHRawp2oPn5R+0mk5QIozgswDz5K54TWyRl5MZuIMX4ANuRxACCHEkBRKIG1WSl0MPI2xhP9NoDmsoxJC9LkoRxSTkiYxKWlS0LUW3cKhmkPB9aulGyg4vIXH196P5n7f/TazzegOEJXZaUlAQDnASbBs9TLykvPIz+x6N2zBngIKiwpZOiuEnbVCCCHCKpRA+i3g7taHBlYTfOKSEGIIMSkTKREppESkMDttduDF2kPUr7qIz4tWsSf+THbHL2bPkQO+koDOygGiHFFdhtWMqAzsFvsXGl9ech5L/rWE5Rcs7zSUFuwp8F0XQgjR93oMpFrrvcD54R+KEGJQcCZhn/8WozbdyKitd4CpBOb8CzyZvlvaygE61q9uKd3CSztfoqG5wXevQrV3B/DbZNX2a5I3KagcID8zn+UXLPcLne3trvzDaHczqEIIIXpPj4FUKeUALgdOwe9Pda31ZWEclxBiIDNZYOIfIO5UeP/b8OpkmPkopJ4LhFYO4L/JaneVURbw5u43KaopCugOYDfbSY9KDzqONSs6i3+c9w+W/GsJk22PEuWIkjAqhBD9VChL9o8B24FFwG0Yu+y3hXNQQohBIvU8OHM9rLoQ3j0Pcn8B439rBNYu+JcDzEmfE3S9rqmOz6s+bw+rfrOsHxz4gKq6qoD73VY3myo34bF5+MozN/Dk156UMCqEEP1MKIF0hNb6QqXU+VrrR5RSTwIrwj0wIcQg4cmChavhox/D1jvg8Acw62lwJp7QxzksDkbHjWZ03OhOr1fWVgaF1VfW2qlpqKFaVfPVZ77KqcNPZVH2IhaNWMTExInSAUAIIfpYKIG0sfXXKqXUWIyepBlhG5EQYvAxO2Da3yBuFhReDa9OMkJpwtyT/ltFO6OJdkYzOWkyYNSMvvT+etIi06jTkSwesZjth7dzw1s3cMNbNzDMPYwFWQtYlL2IhdkLSfAknPQxCSGE6F4ogfQBpVQ08GvgRcAD3BTWUQkhBqesb0PMJFj1dXjrdJjwexizNGynO7XVjE6ON2pI71j4b18N6Zj4MazctZIVu1awYtcKntj8BAATEycas6fZi5iVNgub2RaWsQkhhGgXyi77B1u/fAfICu9whBCDXtQ4WLwOPrgcPr4eytbAzEfAFnVSfxv/DUz3rzT2Y3bcff/tCd/m2xO+TYtuYcOhDb5weuf7d3LH6jtwW93kZ+b7AuqImBEoORpVCCFOuu7Osv9pd2/UWt918ocjhBgSrBEweznsuAc2/MzYhT/nXxAz+aR8fMfd9Pfzvu9ax1Can5mPSZmYkjyFKclTuGHODVTXV1Owp8AXUF/e+TIAmVGZvtrT0zNPJ8IuR6QKIcTJ0N0Madt59aOBPIzleoBzgXfDOSghxBCgFOT8GGKnweolsPJUmHovZF/xpZfwC4sKu23t1BZKC4sKO70nwh7B+Tnnc36O0YL5s4rPWPGZEU4f2/QY9390PxaThZmpM30BdXLSZNkcJYQQJ6jLQKq1vhVAKbUSmKy1rml9fgvwbK+MTggx+MXPhMXrYc3F8OGVUPYe5P0VLK4T/shQjgPNz8wPuf3TiJgRjJg2gh9M+wENzQ2s2b/GF1BvLLiRGwtuJM4VF7A5KsmbdMLjF0KIoSaUTU1pQIPf8wZkl70Q4mRyxMO8V2HLb2DLbVCxHuY8BxGj+npkQWxmG/My5jEvYx63z7+dkqMlvL77dVbsWsHKXSt5astTAIxPGO+rPZ2dNvsLH38qhBBDSaiN8T9USv0b4yz7rwKPhnVUQoihx2SG8bdA3Ax4/xJ4bSrM+AekXdDXI+tWgieBS8ZfwiXjL6FFt7CxeKOv9vRPH/yJP675Iy6ri3kZ83wBdVTsKNkcJYQQfkLZZf87pdSrQNuRKf9Pa70hvMMSQgxZyYuNJfz3lsB7F8Lon8DEO2AAtF8yKZPvSNTrZ1/P0YajAZujXvn0FQDSI9N9tadnZJ5BpCOyj0cuhBB9q7td9hFa62qlVAywt/XRdi1Ga10R/uEJIYYkdxrMfxc2/Bx2/AnK1xq78l2pfT2yL8Rj83Du6HM5d/S5AOyu3O2rPX1qy1M8sP4BzMrMjNQZvoA6JWkKZpO5j0cuhBC9q7sZ0ieBc4CPMJbq26jW59KTVAgRPmYbTL0b4mfB2suN051OfRKSFvT1yE5YVnQW/5P3P/xP3v/Q2NzI+wfe9wXUm9++mZvevokYZ4xvc9SiEYtI9ib39bCFECLsuttlf07rr5m9NxwhhOggfQlEjYf3LoCCRTDuFhh7IwzwFktWs5XT0k/jtPTT+N0Zv6PsWFnA5qhnPnkGgLHDxvpqT+ekz8FhcfTxyIUQ4uTrsYZUKfVV4C2t9ZHW51HAPK31C+EenBBCABCZA4vWwodXweab4fAamPk4OOL6emQnTbw7nm+N+xbfGvcttNZsKtnkqz2998N7ufP9O3FanMzNmOsLqDlxObI5SggxKISyy/5mrfW/255orauUUjcDEkiFEL3H4oaZj0H8HPjoGnhtMsx+FuKm9/XITjqlFBMSJzAhcQJLZy3lWMMx3t77ti+gXrviWgCGRwwP2BwV7Yzu45ELIcSJCSWQdrYuFsr7hBDi5FIKRl4FsVNh1QXwxhyYdCeM+uGXPt2pP3Pb3Jw96mzOHnU2AHur9vpqT5dvXc6DGx7EpExMT5nuC6h5yXmyOUoIMWCEEizXKaXuAv6MsZnpRxgbnYQQom/ETIHFH8H73zFmS8tWw/S/g9Xb83sHgYyoDK6aehVXTb2KxuZG1h5cy8pdK1mxawW3vnMrt7xzC9GOaOZnzfcF1NSIgdWhQAgxtIQSSH8E/Bp4BmOH/UrgB+EclBBC9MgeA3P/A1vvgE03QtXHMPs5iDqlr0fWq6xmK7PTZjM7bTa35d9G+fFy3tj9hm95/9mtxknPufG5vtrT09JPw2l19vHIhRCiXSiN8Y8B1yulIoAWrfXR8A9LCCFCoExwyi+N051WfwNWTINpD0DmxX09sj4T64rlorEXcdHYi9Ba80nZJ77l/b8U/oX/++D/cFgcnJZ+mi+g5sbnyuYoIUSf6rFvilJqnFJqA7AZ+EQp9ZFSamz4hyaEECFKyIfFG4yl/PcvgRUzoGhF9+8pKYCty3pnfH1EKcXYYWO57tTrWHnpSip+UcGrF7/K1VOuZv+R/Vy38jrG/nUsw/9vOJf/53KWf7Kcilo580QI0ftCWbL/G/BTrXUBgFJqHvAAcGoYxyWEEF+MKxnOeAs23gDb/gjvnAMzH4WMbwbfW1JgHE06e3nvj7MPuawuFo9YzOIRiwHYd2Sfr/b0+e3P84+P/4FJmchLzvPVnk5LmYbFJPtYhRDhFcqfMu62MAqgtX5bKeUO45iEEOLEmCwwaRnEnQprLoE1F8PRPUB++z3+YTQhv8uPGgrSItO4YvIVXDH5Cppamig8WOirPf3tqt9y27u3EWmPDNgclRaZ1tfDFkIMQqEE0t1KqV8Dj7U+vwTYE74hCSHElzT8K3DWRuNkp02/guqHwDsSit+C1RdJGO2ExWRh5vCZzBw+k1vm3UJFbQVv7n7TF1Cf2/YcADlxOb7a07kZc3FZXX08ciHEYBBKIL0MuBV4HmOX/bvA/wvnoIQQ4kvzZsNZm40jR3eVQl0pvPVLsMfBhqXgSARngvGrIxGcieBIaP/a4hnUvU17EuOM4cJTLuTCUy5Ea822w9t8m6P+9tHfuHvt3djNduakz/EF1LHDxsrmKCHECQlll30lcE0vjEUIIU4uixPm/RcK/2EE0rhTjZnSuhKoPQAV66C+FHRL8HvNLiOgOrsIrI625wnG7zOIKaXIjc8lNz6Xa2deS21jLav2rfIF1J+//nN+/vrPSfYmszB7IQuzFrIgewFxrsFztKsQIry6DKRKqZcwGuF3Smt9XlhG1A/tW7YPb56X6Pyuj+WrLKikprCGtKVSXyVEv1JSAA2V4E6Hmp0w4beBy/UtzdBQDrXFUFfc+muJ39fFxvvK3oX68s5/D2tkYGDtMsDGg8naO993GDmtTiN4Zi/kTu7kQPUB3+ao/2z/Dw9//DAKxZTkKb7Z0xmpM7CaB/73LoQIj+5mSP+310bRz3nzvGxdspXc5bmdhtLKgkrfdSFEP9K2gSnyUbBGGbWjHTc0mczgGGY8GN/95zU3GDOqdSVdB9jKDcbzxurOP8Me1/2Ma9vX9lijz+oAkBqRymWTLuOySZfR3NLMuqJ1vtrT29+7nd+t+h0R9ghOzzzdF1AzozP7ethCiH6ky0CqtX6n7WullBNI01rv6JVR9TPR+dHkLs/tNHT6h9HuZlCFEL3Mfzf9Cw7jtYT8zkNpqMw2cKUaj540HfcLrh1mXNter/nMeN5cF/x+1RaUeygZcCYaM7T9pHbTbDIzPXU601Onc9Pcm6iqqwrYHPXC9hcAGBkz0rdzf17GPDw2Tx+PXAjRl3qsIVVKnYsxW2oDMpVSE4HbhtKSPQSG0uRLoWi0ScKoEP1VUGun99uvfdlQGiqLCzyZxqM7WkNTTTclA61fV20yvtZNwZ9hsvc849p23dK7XfuiHFF8PffrfD3362it2VG+w1d7+tCGh7iv8D6sJuP407aAOiFhgmyOEmKICWWX/S3ANOBtAK31x0qpjLCNqB9rC6VnnrWRz8cqtu6VMCpEv1Re2H3YbAul5YV93/5JKbBGGI+IUd3fq1uMetju6l2P7YHyD6CujE63AVg8oZUMOBKMGeGT+q0qcuJyyInL4cczfkxdUx3v7XvPF1Cvf/N6rn/zehLcCSzMXsii7EUsyF7AMPewkzoOIUT/E0ogbdJaH5GfVg1R86Koc8HIdRr7FDsR0yL6ekhCiI5yl/Z8T0J+34fRL0qZjNpSeyxwSvf3tjRBfVnXM661xXDkEyh5ywi5nbFFdxNY/dpm2eONWtwvyGFxMD9rPvOz5vNH/khRTZFvc9Qrn77CY5uM9teTkyb7ak9nDp+J7SQHZSFE3wslkG5RSn0LMCulRmK0gFoT3mH1X1VvV2FtgOJMReJHR1mbs5ax/x5LxFQJpkKIfsRkAWeS8ehJc3339a51xVD+ofFr07Hg9yuTEUpDKRmwxXRZ75rsTea7E7/Ldyd+l+aWZtYfWu+rPV22ehm3v3c7HpsnYHNUdkz2l/wHJYToD0IJpD8CfgXUA08CK4DfhnNQ/VVbzejK71kpGm3if4+msmvpLtZPX0/mbZmkXZ+GMstMshBigDHbwZ1mPHrSeLTrGde2AFu93fi6pSH4/SarX3usLmZcHYmYnYnkJU8lLyWPG0+7kSN1R3hrz1u+gPrijhcByI7O9tWe5mfk47V7T/I/HCFEbwglkI7WWv8KI5QOWf4bmIp2bgdg+HXDcYx0sHXJVvbcuIfyV8sZ89gYnJmDu0m2EGIIs3qMh7eHmUmtobEKaruYca0thtqDULm+dbNWZ4cTOH2zq5GORL7qSOCr6UnoUb/g08YWVpTsYsXBj3l448P8Zd1fsJqsnDr8VF9AnZg4EdMAaZ0lxFAXSiC9SymVBDwLPK21/iTMY+p3gnbT72y/Fn9ePONeGccnX/mEoxuOsm7COkbeO5KEbyfILlEhxNCllFGDaouGyJzu7w04nKCrFlnthxMoYFTr40dmqE+D1U1uVtTZWHF4HTd8/g43vHUDw+xuFiSNY1H6LBaOWERCTK7RSmsQHE4gxGATytGh+UqpRGAJ8IBSKgJ4Rms9ZJbtawprut2ajwrPAAAgAElEQVRNH3N6DGP/M5bKlZUcWXOE7d/dTvnL5Yy6fxTWWPmDTwghuhVwOEEPWhqNY2Drin2zr/a6Yk6vLeb0uhLuqCumuPoAK8uLWFFzjBX7P+CJvR/AO3cy0Q6LXLAoMoJZManYXEndHw07gA4nEGKgC2WGFK11MXCPUqoAWArcxBCqIw3lONDo/Gii86PRzZr9d+5nz417OLLmCDkP5xCzIKYXRimEEEOAyQquFOPRhUTg28C3m2ppqT3Ehv3vsmL3G6zY/yF3lu3ijspq3Pt3kB9xiEUuE4vsRxlhqg/eaxV0OEE3R8P2o8MJhBiIQmmMPwa4CLgAKAeeBq4L87gGLGVWpC1NI3pBNNsu3samhZtI/UkqmbdnYnZ88bYoQgghTpDFicmbxZTcLKbkfpcbgOr6agr2FPg2R718cDcAmVHpLEo7lUXJYzk9NoWI5urg8oEjm42vuzqcoLOuAp11HejlwwmEGAhCmSH9J/AUsFBrXRTm8Qwa3klepnw0hd1Ld3PgTweofKOSMU+MwTNejscTQoi+EmGP4Pyc8zk/53wAPqv4zNeY//HtL3H/pqewmCzMTJ3ZujnqaiYnTW7fHBVwOEEX9a7H9oZ4OEE3M66ORGN21mzvtX82QvSlUGpIZ/TGQAYjs9PMyHtHEnNWDDsu28FHeR+RdXsWqT9JRZlkaUcIIfraiJgRjJg2gh9M+wENzQ2s2b/GF1BvLLiRGwtuJM4Vx4KsBSzKXsTC7IUkeZO+wOEEh7s/EvbI1i9wOEE3fV5P8HACIfqLkGpIxZcTe2YsUzdNZeeVO9l13S7K/1tOziM5OFIdfT00IYQQrWxmG/My5jEvYx63z7+dkqMlvL77dVbsWsHKXSt5astTAIxPGO9rzD87bTZ2SxezmCaLERididDTCdPN9X6btbqYfS1f13o4wdHg9ysT2ON6mHFtDbDdHE4gRF+RQNpLbPE2Tnn+FIr/UcynP/6UdePWMer+UQy7SM5oFkKI/ijBk8Al4y/hkvGX0KJb2Fi80Vd7+qcP/sQf1/wRl9XFvIx5voA6KnbUibX8M9vBPdx49KTpmN/JWl0E2Jodxtct9cHv9x1O0M2Ma1tJgcUr4VX0ipADqVLKC2itdSc/molQKKVIujyJqHlRbLtkG1u/sZXyl8sZed9ILJHys4EQQvRXJmViUtIkJiVN4vrZ13O04Shv733bt7z/yqevAJAeme5rzH9G5hlEOiJP/mAsbvBkGY/uaA2NR7qvd60taj2coBR0c/Bn+B1O0H2ATQCLHAojTlwou+zHAY8CMcZTVQZ8R2u9JYT3LgbuBszAg1rrP3RyzxLgFozK741a6299oe9gAHJmO5m4aiL7fr+PvbftperdKsY8Noao06L6emhCCCFC4LF5OGfUOZwz6hwA9lTu8c2ePrXlKR5Y/wBmZWZG6gxfQJ2SNAVzb9Z5KgW2KOPR0+EEugXqy7svGaj5DMreM+piO2ON6HnGtW2zlhxOIDoIZVrub8BPtdYFAEqpecADwKndvUkpZQb+DCwADgCFSqkXtdZb/e4ZCfwSmKW1rlRKDZn1a5PFRMZNGcQsimHbJdv4eN7HpP0ijYxbMzDZpBGzEEIMJJnRmVw99Wqunno1jc2NfHDgA19Avfntm7np7ZuIccYEbI5Kiei6l2qvUyZwxBuPqHHd39vSaHQQ6GzGta71UbXReN54pPPPsMd1P+PaNiNrj+u9wwm2LoPYPEjI7/qekgIoL4Tcpb0zpiEklEDqbgujAFrrt5VSoTRRmwZ8prXeDaCUeho4H9jqd8/3gD9rrStbP7s05JEPEhHTI5iyYQq7frqLfX/YR8WKCsY8MQb3GOlTJ4QQA5HVbGVO+hzmpM/ht6f/lrJjZbyx+w1fQH3mk2cAOCX+FN/s6Wnpp+GwDJCNriYruJKNR0+aaltnW7soGagrhsPvQ+0haK4Nfn/A4QQdZlw7BtgvezhBbB68twRmL+88lJYUtF8XJ10ogXS3UurXwGOtzy8B9oTwvhRgv9/zA8D0DveMAlBKrcZY1r9Fa/1aCJ89qFg8FkY/MJrYs2PZccUOPpr8Edn/m03y95NPrDheCCFEvxHvjueb477JN8d9E601m0s3+2pP7yu8j7s+uAuHxRGwOSonLmdw/PlvcYInw3h0R2uje0B39a51xXBki/G8pTH4M0y2nmdc277u7HCChHwjbPpCp98PCP5htLsZVHHCQgmklwG3As+3Pn8X+G4I7+vs/6SOHYItwEhgHpAKrFJKjdVaVwV8kFJXAlcCpKX1fIznQBV3fhze6V52XLaDT3/4KeX/LWf0P0ZjT5TGyEIIMRgopRifMJ7xCeP5+ayfc6zhGO98/o4voF674loAhkcMD9gcFe3sqW/UAKcUWL3GI2Jk9/dqbfRt7a7e9djnUL4W6suM+tiOLJ6uZ1xH/wTe/RrUPQyOOAmjvSSUQDpfa32N/wtKqQuBZ3t43wHAv39FKtDxpKcDwAda60Zgj1JqB0ZALfS/SWv9AEbdKlOnTu3k2IvBw55oZ9x/x1H01yJ2XbeLdePWMfrB0cSdH9fXQxNCCHGSuW1uzhp5FmeNPAuAz6s+9y3tP7v1WR7c8CAmZWJ6ynRfQM1LzuvdzVH9jVJgjzEekbnd39vS3OFwgk7qXau3GaGzoSLwvdXboBpY9b8w51kJo2EWSiD9JcHhs7PXOioERiqlMoGDwDeAjjvoXwC+CTyslIrDWMLfHcKYBjWlFCnfTyEq32gPteUrW0i6Igl7mp3I2ZFE53f9k3JlQSU1hTWkLR28M8lCCDFYpUelc+WUK7lyypU0tTSx9sBaX0C99Z1bueWdW4h2RDM/a74voKZGpPb1sPsvk9nY3e9MgOgJ3d/rO5ygdcZ1/yFoKIeR/yNhtBd0GUiVUmcCZwEpSql7/C5FAE09fbDWukkp9UNgBUZ96D+01p8opW4D1mmtX2y9tlAptRVoBn6utS4/8W9ncHGPcTP5/cnsvWUv+/6wD1uSjQN3HeCU50/pNJRWFlSydclWcpf38BOjEEKIfs9isjArbRaz0mZxW/5tlB8vD9gc9exWY14oNz6XhVkLWTRiEXPT5+K0Sj/QE+J/OEFJATRVgzsdPrsGEk+XUBpm3c2QFgHrgPOAj/xerwGuDeXDtdavAK90eO0mv6818NPWh+iEyWYi6/dZxJwZw7ZLt9FU3cTmczYz9j9jA+7zD6PdzaAKIYQYmGJdsVw09iIuGnsRWms+KfvEV3v613V/5U9r/4TdbOe09NN8s6enxJ8yODZH9aa2mtHIR8EaFbjRSUJp2HQZSLXWG4GNSqknW2s8RR+KmhNF3sY8Pv3Rp5Q8VsLmMzcz8hIzn84wSxgVQoghRinF2GFjGTtsLNedeh3HG4/z7ufv+gLqz17/GT97/WekeFNYmL2QRdmLmJ81n1hXbF8PvX/z38D0Qusu+4677yWUhkUoNaTTlFK3AOmt9yuMyc0eziwTJ5sl0sKYR8cQe3Ys2y/fzhkPNzFpZRObKzYT/414jm87TmNpI5ZYC9ZYK9Y4K9ZYK2bXEC5+F0KIIcBldbF4xGIWj1gMwP4j+31L+//e/m/++fE/USjyUvJ8raWmp07HYpJjq32CdtO/335NQmnYhfJf4kMYS/QfYdR5ij427KJhRMyK4NW8D4gtAm3TlDxcQsnDJZ3eb3KYjJDaGlD9w2rH8Nr2miXS0q+XefYt24c3zysbvIQQohPDI4dzxeQruGLyFTS1NFF4sNAXUH+36nf85t3fEGmP5IysM3wBNT0qva+H3bfKC7sPm22htLxQAmkYhBJIj2itXw37SMQXUvtpLY6jsO4sMzM/NDHmyTF4xntoPNxIY3nro/XrpvKmgOdHNx01XqtohE7aswEoi8ISYwkIsL7w2kWgtURbMFl654g3b5632zIF2eAlhBAGi8nCzOEzmTl8JrfMu4WK2gre3P2mL6A+v81oMz46drSv9nRu+lzctiF2YmAox4Em5EsYDZNQAmmBUuqPGI3x69te1FqvD9uoRLfawtbK71kpGm3i8p/lnFANqW7RNFU1BQRYX3jtEGhrP6ulca3xXDd03QrWEmUJno3tIdCaHV+8pCA6P5rc5bmdhk6pqRVCiK7FOGO48JQLufCUC9Fas+3wNl/t6QPrH+CeD+/BZrYxJ22OL6COGzauX6+aiYEvlEDadtznVL/XNHD6yR+O6Il/2CrauR0IDmehhjBlUlhjrFhjrMZxBCHQWtN8rLnb8Nr2vKG4gWOfHKOpvInmo11Xe5hcpqCSgZ4CrdlrDvi+ky+FotEmCaNCCPEFKKXIjc8lNz6Xa2deS21jLav2rfIF1KVvLGXpG0tJ8iT5NkctyF5AnEsOaxEnV4+BVGstc9P9RFDY2tl+7URD6RellMLisWDxWCAj9Pe11Le0h9ceZmPrPq8zrld23e5WWZUvvNqSbZxz7zHKk2BzxWbSf5WOI92BbtYos/xEL4QQoXJanSzMXsjC7IXcyZ0cqD7Ayl0rWbFrBS/ueJFHNj6CQjEleYqv9nRG6gysZmtfD10McD0GUqVUAvB7IFlrfaZSKheYqbV+KOyjEwFqCmu6DZttobSmsKbfzQ6a7CbsyXbsyfaQ36ObNY2V3YdX32ysA4bthxZa2HPDHvbcsAeTw4RztBNXjgv3GDeuHBeuMS6cI52YndJ5QAghepIakcplky7jskmX0dzSzLqidb7a0z+89wd+t+p3RNgjOD3zdF9AzYzO7OthiwEolCX7h4F/Ar9qfb4TeAZj973oRaHsFo/Oj+53YfREKbPCFmfDFmfr9r7KgkqKz9vIurPMzFiryLg5A5PTxPHtxzm+7Tg1hTWULS8zCk0AFDgyHb6A6h9YrbHyU74QQnTGbDIzPXU601Onc9Pcm6iqqwrYHPXC9hcAGBkz0ld7Oi9jHh6bp49HLgaCUAJpnNZ6uVLql+A7ElTaP4l+obsNXslXJPvua65tpvbTWo5vO87x7cc5tu0Yx7cfp+qtKlrq2lsNWOOtAUG17VdHmgNlkuV/IYRoE+WI4uu5X+fruV9Ha82O8h2+2tOHNjzEfYX3YTVZmZ022xdQJyRMkM1RolOhBNJjSqlYWueXlFIzgCNhHZUQIfgiG7zMTjOe8R484wN/UtfNmrp9db6g2vZr2XNlNJW317CanCZco4ODqnOk84S6BAghxGCilCInLoecuBx+POPH1DXV8d6+93z1p9e/eT3Xv3k9Ce6EgM1Rw9zD+nroop8IJZD+FHgRyFZKrQbigQvCOiohenCyNngps8KZ6cSZ6ST2rMAj9RoONwQF1er3qyl9urR9+d9kLP/716i6coyHNUaW/4UQQ5PD4mB+1nzmZ81n2YJlHKo55Aunr3z6Co9tegyAyUmTfbWnM4fPxGbuvkRLDF6h7LJfr5SaC4zGODZ0h5xtL/pab2zwssXZsM2xETUnKuD15uPNHN95PCisVrxega5v79FqHWYNrlMd48KeapflfyHEkJLkTeI7E7/DdyZ+hxbdwvpD633L+8tWL+P2927HY/MEbI7Kjsnu62GLXhTKLnszcBZGkx8LsFAphdb6rjCPTYgu9eUGL7PLjHeiF+9Eb8DrullTt7fOV5/qW/5fXhbQwsrkMvlmUf3DqnOEE5O9d066EkKIvmJSJqYmT2Vq8lR+ddqvqK6v5q09b/kC6os7XgQgOzrbV3uan5GP1+7t4ZPFQBbKkv1LQB2wmS4PmhRCKLPCme3Eme2Ec9pf11rTWNbYPqPauqnqyOojlD5Z2n6jGZxZzk43VVmjZPlfCDE4Rdgj+ErOV/hKzlfQWvNZxWe+nfuPbHyEv6z7CxaThVnDZ/kC6sTEiZiU/AA/mIQSSFO11uPDPhIhBimlFLZhNmzDbETN7bD8f6yL5f8VFQFHtNoSbZ0GVXuqXXasCiEGDaUUI2NHMjJ2JD+c9kPqm+pZs3+NL6De8NYN3PDWDcS74n2boxZmLyTBk9DXQxdfUiiB9FWl1EKt9cqwj6af2rdvGV5vHtHRXR9aVVlZQE1NIWlpS3txZGKgM7vNeCd58U4KXIpqaWqhbm/w7v/Sp0ppqvJb/nebghr/u3JcxvK/TWYPhBADm91iJz8zn/zMfP4w/w8UHy3m9V2vs2LXClbuWskTm58AYGLiRF/t6ay0WbI5agAKJZB+APxbKWUCGjE2NmmtdURYR9aPeL15bN26hNzc5Z2G0srKAt91IU4Gk8WEa4QL1wgXnNv+utaaxtLGwDrVbcepeqeKksdL2m80gzPbGdT435XjwhIZyv/2QgjR/yR6Erl0wqVcOuFSWnQLHxd/7Ks9vfP9O7lj9R24rW7yM/N9AXVEzAhZSRoAQvmb6U5gJrBZa617unkwio7OJzd3uV/odPiu+YfR7mZQhTgZlFLYEmzYEmxEzwvcsNV0tInaHbVBm6oqXqlAN/ot/yfZgpb+3WPc2JJt8oe2EGLAMCkTk5MmMzlpMr+c80tq6mso2FvgC6gv73wZgMyoTF/t6emZpxNh73w+bdnqZeQl55Gf2fXf5QV7CigsKmTpLFkNPdlCCaSfAluGahht4x9Kk503UVQ7WcKo6FcsHgveKV68Uzos/ze2ULenLuiUqpLHS2iubj90zew1B+3+d41x4cx2YrLK8r8Qon/z2r2cN/o8zht9HgC7Knb5ak8f3/w49390P2ZlZubwmb7Z0ynJU3ybo/KS81jyryUsv2B5p6G0YE+B77o4+UIJpIeAt5VSrwL1bS8OxbZP0dH5jBnzDGfXn0VFQwZbt5ZLGBX9nslqwjXKhWuUC85vf11rTUNxcPP/yrcqKXmsfflfWRTOEZ3s/h/twhIhy/9CiP4pOyab78d8n+/nfZ+G5gbe3/++L6D+uuDX/Lrg18Q6Y1mQvcAXUJdfsNwvdLavhvqH0e5mUMWJC+Vvkz2tD1vrY0iLiTmdsrpRJLk243LNkTAqBiylFPYkO/YkO9Gnd1j+r2nytajyD6zlL5ejm/yW/1NsnW6qsiXJ8r8Qov+wmW3MzZjL3Iy5/P6M31N6rDRgc9TTW54GYNywcZyecTpffearTHM8QbQzRsJoLwnlpKZbAZRSbq31sfAPqX+rrCwgyrafsrqRwCp27vwRo0bd29fDEuKksngtRORFEJEXWGvV0thC7a7aoKBa/EgxzTV+y/8R5uBTqnJcOLIdmCyy/C+E6FvD3MO4ePzFXDz+Ylp0C5tKNvlqT/+9/d80tjSyqXozDouDN5/9Lc9e+KyE0TAL5aSmmcBDgAdIU0pNAK7SWn8/3IPrb9pqRlcW/YZDtRNYmvdLioruw2KJJCvrt309PCHCzmQ14c5x485xw1faX9da01DUEFCjenzbcSpfr6TkEb/lf2vr8n+HTVWuHBcWjyz/CyF6n0mZmJg4kYmJE/nF7F9wtOEob+99m2uf/Jyahhqunnq1hNFeEMrfAH8CFgEvAmitNyqlTgvrqPoh/w1MRVuMupIJE95g/frp7Nv3OyyWCOlB2oukN2z/opTCnmLHnmIn+owOy/9Hmji+43jApqpjnxzj8H8OQ/ukKvZUe3BQHePCliDL/0KI3uOxeXBb3dQ11ZEemc7fPrqGMzLPkFAaZiFNSWit93f4C6G5q3sHo+Dd9O8DYDY7mDTpXdatm8Lu3b/AbPaQkjLkJo77hPSGHTgskRYipkUQMa3D8n9D6/J/h01Vxf8spvlo+x8xlihLp7v/HZmy/C+EOPnaakYnxz9KlCOKPyxcLjWkvSCUQLpfKXUqoJVSNuAaYFt4h9W/1NQUdhl8LJZIJk9eTWHhZHbt+hlRUfNwu3P7YJRDi/SGHfhMNhPuMW7cY9wBr2utqT9YH3yc6msVFD9c7LtP2RTOkc7gTVWjXZjd5t7+doQQg4D/Bqb7Vxp/r+Rn5gfsvpdQGh6hBNKrgbuBFOAAsBL4QTgH1d/0tORrsyUwefIqNmyYxaZNi5g0aTUOR1ovjW7oCuwNezNFtVMkjA4CSikcqQ4cqQ5iFsQEXGusagza/X9s0zEO//swtLTfZ0+zB8yoJn3aQlWiQmsty/9CiE513E1/f+tqKEgo7Q3dBlKllBm4VGt9cS+NZ8ByOrMYP34FGzacxsaNC5k06T1stri+HtagFxU1j/T0mzi7/qcopdm4URMdvZDjx3dgNnvxeMZjMg35bmWDhjXKSuSMSCJnRAa83lLfQu1ntUGbqg79/RAtx1t8+69W3746uJ9qjgtnphNllqAqxFBWWFTYbdhsC6WFRYUSSMOg20CqtW5WSp0P/F8vjWdA83jGM27cS2zatJDNm89iwoQ3sVi8Pb9RfGEtLfWUli7nwIG7OXr0I8CKWTXhcGRTU/MhlZWvAqCUDY9nPF5vHl7vVLzeqbhcuZhMsqN7MDHZTbhPceM+xU088b7XdYum/kA9v7rzI6IPtbAkdhjHth2j/L/lFP8jcPnfNSo4qLpGuzC7ZPlfiKEglONA8zPzJYyGSSh/K69WSt0HPAP4+pBqrdeHbVQDWFTUHHJzl7Nly1f55JOvMW7cy5hM9r4e1qBRX3+IoqL7KSq6n8bGUlyuMaSk/ITPPv8nH1d8jZlJL5GbuxynM4uamnXU1BRSU7OOkpInKCr6KwAmkxOPZ1JrQM1rDamjUEo2yAw2yqRwpDk4kGviQK6JG68a5bvWWNkY1E/16IajlD1XFrj8n24PavzvGuPCGmeV5X8hhDhJQgmkp7b+epvfaxo4/eQPZ3CIizuXnJyH2L79u2zb9m1yc5/EqH4QJ6q6+kMOHLiHsrLlaN1EbOzZpKRcA5jZtu0iVhb9lqLaKVx+xqW+GtJhwy5g2LALANC6hdrazwJC6qFDD3Lw4D0AmM1evN4pASHV4ciUwDGIWaOtRM6MJHJm4PJ/c12zsfzfYVNV1TtVtNS2J1VLjCWo8b9rjAtHukOW/4UQ4gsK5aQmmZs+AYmJ36Gx8TC7dv2MTz+NY+TI+yTcfEEtLQ2UlT3HwYP3UF39AWazl+Tk75OS8kNcrhGd9obtuPu+bWOTUiZcrlG4XKNISPhW6+c3cfz49oCQeuDAPWjdAIDFEuNb5m8LqXZ7ivx7HOTMDjOesR48Yz0Br+sWTf3++oAa1ePbj1P+YjnFD7Uv/5scJpyjnEEnVTlHOTE75QdTIYToTCgnNf0eWKa1rmp9Hg1cp7W+MdyDG+iGD7+OhoZS9u9fhtUaT2bmLX09pAGhoaGUoqIHKCr6Cw0Nh3A6RzJixL0kJn7HV5PbVW9Y6DqUdmQyWfB4xuLxjCUp6buAEYKPHdsSEFL37buDtta7NltiUEi12YaF8x+H6CeUSeFId+BIdxC7ODbgWmN5++7/tsBas66GsmfLjPUkAAWODEenm6pscbLxTggxtIWyZH+m1vqGtida60ql1FmABNIQZGX9gcbGw3z++a1YrXGkpv6wr4fUb9XUbODAgbspLX0KrRuIiVlMSspDxMQsCqrv7K43LLSH0pqawi/U/slksuH1TsbrnQxcCUBzcy1Hj24MCKnl5f+lLWnY7cP9Nk3l4fVOwWqN7vo3EYOONdZK5KxIImd1svy/szawTdW2Y1QVVNFS1778b42zdhpUHekOlElm5IUQg18ogdSslLJrresBlFJOQHbphEgpxahRf6OxsZzPPrsGqzWOhIRv9PWw+o2WliYOH/43Bw/ew5Ej72EyuUlK+h4pKT/E7c7p8n2hHAcaHZ1/UnqRms1OIiNnEBk5w/daU1MNR49uCAiphw8/77vucGQTEdEeUj2eSdJxYQgyO8x4xnvwjA9e/q/7vC5oU9Xhfx+m8XCj7z6T01j+77ipyjnKidkhy/9CiMEjlED6OPCmUuqfGFNClwGPhHVUg4zJZCE39yk2bVrM9u3fxmqNISZmYV8Pq081NpZTVPR3ior+TH39ARyOLLKz7yIp6TIslsieP6CPWSxeoqJOIyrqNN9rjY2V1NR85AupR46sobT06darCpcrJ2Am1eOZgNns7JtvQPQpZVI4M504M53Enhm4/N9wuCEoqFavrab0mdLA5f9MR6ebqqwx1t7/hoQQ4ksKZVPTMqXUJmA+oIDfaK1XhH1kg4zZ7GTcuBfZsGEuW7Z8jYkT3yQiYnpfD6vXHT26iYMH76Wk5HFaWuqIijqDkSP/QmzsWQO+E4HVGk1MzHxiYub7XmtoKG0NqEZIrahYQUnJo61XzbjdYwNmUt3usdLIf4izxdmwzbYRNTsq4PXm483UflobtKmq8o1KdL323WcdZg3aUOXKcWEfbpflfyFEvxVqd/BtQJPW+g2llEsp5dVa14RzYIORxRLJ+PGvsWHDbDZtOotJk97D7R7T18MKO62bOXz4JQ4evJuqqrcxmZwkJHyH1NQf4Xaf0tfDCyubbRixsWcRG3sWYJzT3tBQRHV1oS+olpU9z6FDDwJtjfwnBMykulw50shfYHaZ8Uzw4JnQYfm/uXX5f1vgpqqyZ8toqmjy3WdymXCN7qT5/0gXJrv04BVC9K1Qdtl/D2N3RwyQjXGm/f3AGeEd2uBktycyYcLK1nPvFzJp0hocjuF9PaywaGys5NChhygq+jN1dXux29PIylpGUtLlWK0xPX/AIKSUwm5PIT4+hfh440BLrTV1dXt9tahGI//HKCr6CwAmkwuPZ5LfTOpUnM6R0shfAKDMCmeWE2eWk9iz25f/tdY0Hm4M6qd6ZPURSp8sbf8AEziznMFBdYwLa5Qs/wshekco0y4/AKYBawG01p8qpaTPzZdgnHv/Ghs2zGXTpoVMnLhqUJ17f+zYVg4evJfi4kdpaTlOZORcsrPvJDb2PJnp64RSCqczE6czk2HDlgBtjfw/DZhJLSr6Gy0tfwLAbI5obeTfHlIdjgzpkSp8lFLY4m3Y4m1EnRa8/H98x/GgWtWKFRXoBr/l/wRrp6dU2VPt8lZloqYAABs0SURBVN+aEOKkCiUd1GutG9r+8FFKWWgvrRcnyOOZ4Hfu/dmt5957en5jP6V1C+Xl/+XgwXuorHwDpewkJFxMauo1eDwT+np4A47RyH80LtdoEhMvAdoa+W8LmEk9cOBPfo38Y/F6pwbMpNrtKX35bYh+yuwy453kxTspsPODbtbU7qkNCqqlT5fSVOW3/O82GeG0w4Yq5wgnJpvM3AshvrhQAuk7SqkbAKdSagHwfeCl8A5raOj83PuBtaGlqekIhw79k4MH76Oubhc2WwqZmb8nKel7g2rWtz8wGvmPw+MZR1LSZQC0tNT7Gvm3zaZ+/vnttDfyT+qkkX98H34Xoj9TZoVrhAvXCBec0/661prG0saAGtXj245zZNURSp/wW/43gzM7+JQqV44LS6SsjgghuhbKnxDXA5cDm4GrgFeAB8M5qKEkLu5cRo9+kB07/l/rufdPDIjd5seP7+DgwfsoLn6Y5uajRETMIivr98TFfRWTSerOeovJZG9dup9CcvJVADQ3H++kkf/LtDfyT/eF1IiIPDyeKVitUd38LmKoU0phS7BhS7ARNTfwv5Wmo01G8/8Om6oqXq1AN7YvptmSbJ02/7enyPK/ECK0tk8tSqkXgBe01mW9MKYhJynpuzQ2Hmb37p+3nnt/b7/8A1rrFioqVnLw4N1UVLyGUjaGDfsGqanX4PVO6evhiVZms4vIyJlERs70vdbUVO1r5N82k3r48HO+607nyICZVKOR/8AtIRG9x+Kx4J3sxTs5cPm/pamFuj11QZuqSp4ooflIs+8+s8fcaVB1jnBissryvxBDRZeBVBmJ6Gbghxj9R5VSqhm4V2t9Wy+Nb8hIS/sZjY2l7N//R2y2eDIybu7rIfk0NdVQXPwIBw/eS23tTmy2RDIybiU5+SpstoS+Hp4IgcUSQVTUXKKi5vpea2ys6NDI/z1KS59qvWrC5RoTMJPqdk/AbHb0zTcgBhyTxYRrpNFWivPaX9da01DSEBRUqwqqKHmsxHefsigc2Y7gTVU5LiwRsvwvxGDT3f/VPwFmAXla6z0ASqks4K9KqWu11v/XGwMcSrKy7qCx8TB7996C1RpPSsr3+3Q8tbW7OHjwPg4d+gfNzdV4vdMYM+YJ4uMvGHC1riKYcWLYAmJiFvhea2go8W2Yqq4upKLiVUpKjIPZlLLgdo8N6JFqNPKXEg0ROqUU9kQ79kQ70fnRAdeaapo63f1f/nI5uslv+T/Z1ukpVbYkW79cXRJC9Ky7QPptYIHW+nDbC1rr3UqpS4CVgATSk8w49/4BGhvL+fTTH2K1xjJs2EW9OgatNZWVb3Lw4D2Ul7+MUmbi45eQmnrNkDxZaqix2RKIjT2b2NizAeO/h/r6g347+wspK/sXhw79HQCl7AGN/CMijEb+A6EOWvQ/Fq+FiKkRREyNCHi9pbGFut11QZuqSh4tobnGb/k/wuybRfUPrI4shyz/C9HPdRdIrf5htI3WukwpJVMiYWKce/80mzYtZtu2S7FYonvl3Pvm5mOUlDzOgQP3cPz4VqzWYaSn/5rk5Kuw25PD/vuL/kkphcORisORSnz8V4G2Rv57OjTyf5Sioj8DRiN/r3dyQI9Up3OENPIXJ8xkbT1larSLuPPbu3dorWk41BAwo3ps2zEq36yk5FG/5X+rwjnCGVyrOtqFxSvL/0L0B939n9hwgtfElxR87v1bRERMC8vvVVu7l6KiP3Po0IM0NVXh8UwmJ+cRhg27CJPJHpbfUwxsRiP/LJzOLN8MvtYtHD++MyCkFhXdT0tLLQBmc2QnjfzTZXlVfClKKezJduzJdqJP77D8X926/O9fq7rtOOUvBS7/21PtnW6qsiXK8r8Qvam7QDpBKVXdyesKkJ0NYdZ+7v2s1nPvV520c++11lRVvcPBg/dw+PB/AEV8/Ndbl+VPlT+ExRemlAm3Owe3O4fExEuBtkb+Wzs08r8LrRsBsFrjAvqjGo38ZTZenByWCAsReRFE5HVY/m9ooXZ3bdCmquJ/FtN81G/5P9IcVKPqynEZy/8Wme0X4mTrMpBqraUIrI+1nXu/fv0sNm1axKRJq7/UuffNzbWUlj7JgQP3cOzYJiyWWNLSfkFy8vdxOFJP4siFaGvkPx6PZzxJSZcDRiP/o0c3B/RIraj4Pe2N/JODQuqJHLCwb98yvN48oqPzu7ynsrKAmppC0tKWntD3JwYmk82EO8eNO8cd8LrWmoaihoAa1ePbjeNUix8u9t2nrArnSGfQpirnaCcWjyz/C3Gi5P+efs7pzGb8+Nf4+OO5raF0FVZr7Bf6jLq6/RQV/ZWiogdoairH7R7P6NEPMWzYNzGbnWEauRDBTCY7ERFTiYiYClwNtDXy/7hDI/+XaGvk73BkdDhtagoWS2S3v4/Xm8fWrUvIzV3eaSitrCzwXRcCWpf/U+zYU+zEzI8JuNZ0pMkIqX6bqo5tPsbhFw63/SwFgH24PWjp3z3GjXWYVVaehOiBBNIBwOudyLhxL7Fx4xmsXz+bKVMKu2xa3jbrM3z4z6muXsOBA3dTVvY8oImL+wqpqdcQGXma/OEo+g2jkf+pREae6nutqamampr1ASG1rOxfvutO56gOIXUSZnP7jFd0dD65ucv9Qmd7lZF/GO1uBlWINpZICxHTI4iY3sny/2e1QZuqDj10iJZjLe3vj7IEBVXXGBfOTCfKLH8WCwFhDqRKqcXA3YAZeFBr/Ycu7rsAeBaj5+m6cI5poIqKOo309JvZu/fXfPxxPpMnrw66p+0v2sTEy/noo6kcPboeiyWK4cN/SnLy93E6M3p/4EKcAIslgujoeURHz/O91thY3qGR/7uUlj7ZetWE250bEFIjImb6Qmmy82aKaqdIGBUnlclmwp3rxp0bvPxff6A+qJ9qxasVFP/Tb/nfpnCNCg6qrlEuzG6pmhNDS9gCqTIaEf4ZWAAcAAqVUi9qrbd2uM8LXAOsDddYBouMjBtpbq5m//4/smnTYuA3gFFcX1r6LNu3fxelbOzffwcuVy6jRt1PQsIlATNHQgxUVmssMTELA9qg1dcX+zZM1dQUUl7+X4qLHwbaGvmPw+udwZmpS9lx5Cy2bn1HwqgIO6UUjuEOHMMdxCwIXP5vrGwM2v1/dONRyp4vg/ZJVezp9k43VVnjZflfDE7hnCGdBnymtd4NoJR6Gjgf2Nrhvt8Ay4CfhXEsg0Z29jKamo78//buPcjOur7j+Pu7t+zZZJtsLqiQhIS7QShIoFSKXCugFKjVCq13GUZbb/VW1KpTnGlFnWodtSOFjFZRGoHWlKaChQSwxZAgKMSIRUWMARNIFgJJIJt8+8c+Sc5eEhLI2d/J7vs1w+xznvPb53zPkj355Hd5fjz88BWctf+HuXvtG7nnno/R23szAFOmnMH06e9h0qTT/dDSqDdu3AsZN+5cpk49F9h2I/+VA27k/8QT36ejZSNH9VzH/vt/zDCqotp72pl44kQmnjhwHvTWp/uH/wcvqlp1+yq2bqgb/p/cNmSOatcRXXTO6nT4X/u0RgbSA4Bf1z1eCQzY6icijgVmZOYNEWEg3U2HH/4V+vp6gfkc1H07vb3B1Kmv4eCDP0WtdnDp8qRi+m/kP4POzhlMm/ZqANauvYU7776A+x8/h/b2f2LSpNMMpWo6LeNaGH/keMYfOWj4f2s1/L9i4KKqx/7jMR65qm74f1w1/D94rurhXbTWHP5X82tkIB3un2rb70Yc/du2fA5487NeKOIS4BKAmTNn7qXy9m1z5lzD1Qt/zvTxdzFjxgc5+ODLS5ckNZ116xaxYsXruPE3f8eqjcfxxtPe7hxS7VOiJeic2UnnzE4mnzVo+H/t5u2r/7cF1vV3rWfNtXXD/wGdB3YOu6iqY2rHyL8haScaGUhXAvU3zZwOrKp73A28BFhcDS2/EFgQEecNXtiUmVcAVwDMnTs3Eb29i5ky7ucse/QttLfPY/Lks/0LVqpTv4Bp1X39q+wHr773d0b7svbJ7Ux82UQmvmzg8P+WTVvY+H8bhyyq6l3cy9aNdcP/U9qGzFHtenEXnQd2Ei0O/2tkNTKQLgUOjYjZwG+AC4E/2/ZkZj4ObL/jdUQsBj7gKvtnt+0v2ptWfZJVG4/jbWe8wb9gpTpDV9Pfsf05Q6lGu9bOViYcNYEJRw28PWBuTTY9tGlIUH30O4+y+crN29u1dLZQO7w2ZFFV7dCaw/9qmIYF0szsi4h3AjfSf9uneZm5PCIuA5Zl5oJGvfZoZq+P9OzWr1+6y9+Fbb8z69cv9fdFY0a0BLVZNWqzakw5e+AGK5sf2zxgjuqGFRtYv3Q9a+av2THZLqBzduewi6rap7SP/BvSqNLQ+5Bm5kJg4aBzH99J21MbWctoYK+PtHt2ZzvQnh4XN0nbtE9pZ+JJE5l40qDh/43V8P+gRVW9t/SyddOO4f/2ae1D76d6RBedMx3+1+5xp6Z9iL0+kqSR1FprZcLRE5hw9KDh/y3V8P+KgYuq1ly3hr7H+ra3a6m10HX4MKv/D+uiZVzLSL8dNTED6T7EXh9JUjOI1qA2u0Ztdo0prxw4/P/Mo88MCapP3PEEq69ZvWP4v6V/+H+4RVXtPQ7/j0UGUkmStNd0TO2g4+QOJp08acD5LRu2sOFnG4aE1bXfW0s+veMGOu37tQ+Zo9r14i7GTR/n8P8oZiCVJEkN19rVSvcx3XQf0z3gfG5JNj24acguVWvmr+HhdQ9vb9cyvhr+H7SoqnZojZaO5z/8/9CnH6L7+G56TuvZaZt1i9axful6Zn7Ie6LvbQZSSZJUTLQGtYNr1A6uwbk7zmcmm9dsHrKg6vHvP87qb67e0bAVagfVhs5TPaKL9km7P/zffXw3P/nTnzBn/pxhQ+m6Reu2P6+9z0AqSZKaTkTQsV8HHft1MOmUQcP/T21hw/0Dh/43/HQDa7+7lnxmx/B/xws7ht2latwB46g25dmu57Qe5syfM2zorA+ju+pB1XNnIJUkSfuU1vGtdL+0m+6XDhz+39q3lU0PDl39v/pbq+nr3bH6v3VC6/Ze1PqgOvGkidtDad8HttI2qc0wOkIMpJIkaVRoaWuh65Auug7pgj/acT4z2bx688B5qis20HtrL7/9xm93NGyF2iE1aofVeOre1bT1tLH8b5Zz5PwjDaMNZiCVJEmjWkTQ8YIOOl7QQc+pA4Nl35N9bLx/45BFVbk12fzYZl508WzD6AgwkEqSpDGrbUIb3cd1033cjuH/dYvW0fLVtbTv18EjVz7C5FdMNpQ2mNskSJIkVbbNGe2aM57aIbXtc0rXLVpXurRRzUAqSZLEwNX0bZP6B5HrV98bShvHQCpJksa8Xa2mN5Q2noFUkiSNeeuXrt/lrZ22hdL1S9ePcGVjg4uaJEnSmLc724H2nNbj4qYGsYdUkiRJRRlIJUmSVJSBVJIkSUUZSCVJklSUgVSSJElFGUglSZJUlIFUkiRJRRlIJUmSVJSBVJIkSUUZSCVJklSUgVSSJElFGUglSZJUlIFUkiRJRRlIJUmSVJSBVJIkSUUZSCVJklSUgVSSJElFGUglSZJUlIFUkiRJRRlIJUmSVJSBVJIkSUUZSCVJklSUgVSSJElFGUglSZJUlIFUkiRJRRlIJUmSVJSBVJIkSUUZSCVJklSUgVSSJElFGUglSZJUlIFUkiRJRRlIJUmSVJSBVJIkSUUZSCVJklSUgVSSJElFGUglSZJUlIFUkiRJRRlIJUmSVJSBVJIkSUUZSCVJklRUQwNpRJwdEfdHxAMRcekwz78vIn4SET+OiJsj4sBG1iNJkqTm07BAGhGtwJeAc4A5wEURMWdQs7uBuZl5NHAt8OlG1SNJkqTm1Mge0hOABzLzF5n5DHANcH59g8xclJkbqoc/AKY3sB5JkiQ1oUYG0gOAX9c9Xlmd25m3Af813BMRcUlELIuIZWvWrNmLJUqSJKm0RgbSGOZcDtsw4vXAXOAzwz2fmVdk5tzMnDtt2rS9WKIkSZJKa2vgtVcCM+oeTwdWDW4UEWcCHwVOycynG1iPJEmSmlAje0iXAodGxOyI6AAuBBbUN4iIY4GvAOdl5uoG1iJJkqQm1bBAmpl9wDuBG4EVwPzMXB4Rl0XEeVWzzwATgG9HxD0RsWAnl5MkSdIo1cghezJzIbBw0LmP1x2f2cjXlyRJUvNzpyZJkiQVZSCVJElSUQZSSZIkFWUglSRJUlEGUkmSJBVlIJUkSVJRBlJJkiQVZSCVJElSUQZSSZIkFWUglSRJUlEGUkmSJBVlIJUkSVJRBlJJkiQVZSCVJElSUQZSSZIkFWUglSRJUlEGUkmSJBVlIJUkSVJRBlJJkiQVZSCVJElSUQZSSZIkFWUglSRJUlEGUkmSJBVlIJUkSVJRBlJJkiQVZSCVJElSUQZSSZIkFWUglSRJUlEGUkmSJBVlIJUkSVJRBlJJkiQVZSCVJElSUQZSSZIkFWUglSRJUlEGUkmSJBVlIJUkSVJRBlJJkiQVZSCVJElSUQZSSZIkFWUglSRJUlEGUkmSJBVlIJUkSVJRBlJJkiQVZSCVJElSUQZSSZIkFWUglSRJUlEGUkmSJBVlIJUkSVJRBlJJkiQVZSCVJElSUQZSSZIkFWUglSRJUlEGUkmSJBVlIJUkSVJRDQ2kEXF2RNwfEQ9ExKXDPD8uIv61en5JRMxqZD2SJElqPg0LpBHRCnwJOAeYA1wUEXMGNXsbsC4zDwE+B1zeqHokSZLUnBrZQ3oC8EBm/iIznwGuAc4f1OZ84GvV8bXAGRERDaxJkiRJTaaRgfQA4Nd1j1dW54Ztk5l9wOPAlAbWJEmSpCYTmdmYC0e8FjgrMy+uHr8BOCEz31XXZnnVZmX1+OdVm8cGXesS4JLq4eHA/Q0pes9MBR4tXQTNU0cz8GehXfHPh6Q9tTc+Nw7MzGl7o5jRrK2B114JzKh7PB1YtZM2KyOiDZgIrB18ocy8AriiQXU+JxGxLDPnWkfz8GehXfHPh6Q95efGyGnkkP1S4NCImB0RHcCFwIJBbRYAb6qOXwPcko3qspUkSVJTalgPaWb2RcQ7gRuBVmBeZi6PiMuAZZm5ALgK+HpEPEB/z+iFjapHkiRJzamRQ/Zk5kJg4aBzH6873gS8tpE1NFCzTCFoljqagT8L7Yp/PiTtKT83RkjDFjVJkiRJu8OtQyVJklSUgXQPRcSMiFgUESsiYnlEvKdgLa0RcXdE3FCqhmYREX9V/f+4LyK+FRGdpWtSGRExLyJWR8R9g86/q9rKeHlEfLpUfZKaU0R0RsSdEfGj6nPib6vzV1efHfdVny/tpWsdjQyke64PeH9mvhg4EfjLYbZEHSnvAVYUeu2mEREHAO8G5mbmS+hfROcCubHrq8DZ9Sci4jT6d4Y7OjOPBD5boC5Jze1p4PTM/F3gGODsiDgRuBo4AjgKqAEXlytx9DKQ7qHMfDgzf1gdr6c/EA7egarhImI68CrgypF+7SbVBtSq+9l2MfSetxojMvM2ht7P+B3ApzLz6arN6hEvTFJTy35PVg/bq/8yMxdWzyVwJ/33VddeZiB9HiJiFnAssKTAy38e+BCwtcBrN5XM/A39PV4PAQ8Dj2fmTWWrUpM5DDg5IpZExK0RcXzpgiQ1n2oq3D3AauB7mbmk7rl24A3Ad0vVN5oZSJ+jiJgAXAe8NzOfGOHXPhdYnZl3jeTrNquI6KF/OHY2sD8wPiJeX7YqNZk2oIf+aTYfBOZHRJQtSVKzycwtmXkM/b2gJ0TES+qe/jJwW2beXqa60c1A+hxU/0q6Drg6M68vUMJJwHkR8SBwDXB6RHyjQB3N4kzgl5m5JjM3A9cDLytck5rLSuD6atTtTvpHFqYWrklSk8rMXmAx1Xz0iPgEMA14X8GyRjUD6R6qelWuAlZk5j+UqCEzP5yZ0zNzFv2Ld27JzLHcI/gQcGJEdFX/f87AxV4a6N+B0wEi4jCgA3i0aEWSmkpETIuISdVxjf7Ojp9GxMXAWcBFmTnmp8k1SkN3ahqlTqJ/Dsm91TwTgI9Uu1KpgMxcEhHXAj+k/y4Id+PuGmNWRHwLOBWYGhErgU8A84B51a2gngHelO4KImmgFwFfi4hW+jvs5mfmDRHRB/wKuKOa6XN9Zl5WsM5RyZ2aJEmSVJRD9pIkSSrKQCpJkqSiDKSSJEkqykAqSZKkogykkiRJKspAKqkpRMSWiLgnIu6LiG9HRFfpmnZXRCyOiLnDnJ8bEV+ojs+LiEtHvjpJan7e9klSU4iIJzNzQnV8NXBX/eYT1aYH0Yw3po6IxcAHMnNZ6VokaV9kD6mkZnQ7cEhEzIqIFRHxZfo3PpgRERdFxL1VT+rl274hIs6OiB9GxI8i4ubq3PiImBcRSyPi7og4vzp/ZETcWfXI/jgiDq3Ov6+67n0R8d66a/xndd37IuJ1O6n5tdU1fxYRJ1ffe2pE3FAdvzkivlgdHxgRN1evfXNEzGzMj1GS9g3u1CSpqUREG3AO8N3q1OHAWzLzLyJif+By4DhgHXBTRFwA/A/wz8DLM/OXETG5+t6P0r+17lurLQHvjIj/Bt4O/GNmXh0RHUBrRBwHvAX4PSCAJRFxK3AQsCozX1XVN3Enpbdl5gkR8Ur6d4c6cxdv84vAv2Tm1yLircAXgAv27CclSaOHPaSSmkWt2o53GfAQcFV1/leZ+YPq+HhgcWauycw+4Grg5cCJwG2Z+UuAzFxbtX8FcGl13cVAJzATuAP4SET8NXBgZm4E/gD4t8x8KjOfBK4HTgbuBc6MiMsj4uTMfHwn9V9ffb0LmPUs7/X3gW9Wx1+vXluSxix7SCU1i42ZeUz9iWrf6KfqT+3kewMYbkJ8AH+SmfcPOr8iIpYArwJujIiLd3btzPxZ1Xv6SuDvI+Kmnexj/XT1dQt7/tnqZH5JY5o9pJL2JUuAUyJiakS0AhcBt9Lf43lKRMwGqBuyvxF4V7Ugiog4tvp6EPCLzPwCsAA4GrgNuCAiuiJiPPDHwO3VNIENmfkN4LPAS/fC+/hf4MLq+M+B7++Fa0rSPsseUkn7jMx8OCI+DCyiv0dzYWZ+ByAiLgGuj4gWYDXwh8Angc8DP65C6YPAucDrgNdHxGbgEeCyzFwbEV8F7qxe7srMvDsizgI+ExFbgc3AO57PW6i+vhuYFxEfBNbQP3dVksYsb/skSSMgIt4P/E5mfqJ0LZLUbOwhlaQGi4i3A28GXl24FElqSvaQSpIkqSgXNUmSJKkoA6kkSZKKMpBKkiSpKAOpJEmSijKQSpIkqSgDqSRJkor6f/rrkFlemRcdAAAAAElFTkSuQmCC\n",
"text/plain": [
"<Figure size 720x504 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"for dist in [1,2]:\n",
" v1 = grouped_aggL.loc[(0,dist,100.0,slice(None))]\n",
" v2 = grouped_aggL.loc[(1,dist,100.0,slice(None))]\n",
" aux_aggL = v1['Ti'].values / v2['Ti'].values\n",
"\n",
" colors = ['r', 'orange', 'g', 'm', 'y']\n",
" markers = ['+', 'x', '1', '2', 'X']\n",
"\n",
" f=plt.figure(figsize=(10, 7))\n",
" ax1 = f.add_subplot(111)\n",
" plt.xlim(0, max(values)+1)\n",
" plt.ylim(0, 1.2)\n",
" plt.xticks(values)\n",
" ax1.set_ylabel('Decremento velocidad')\n",
" ax1.set_xlabel('Procesos hijo')\n",
" ax1.set_title(\"Aumento de velocidad en las iteraciones al realizar redistribuciones asíncronas\")\n",
"\n",
"\n",
" for i in range(len(values)):\n",
" numP = values[i]\n",
" c = colors[i]\n",
" \n",
" mini = i * (len(values)-1)\n",
" maxi = (i+1) * (len(values)-1)\n",
" array_values = aux_aggL[mini:maxi]\n",
" indexes = np.arange(len(values)-1)\n",
" aux_j=0\n",
" for j in range(len(values)):\n",
" if(values[j] != numP):\n",
" indexes[aux_j] = values[j]\n",
" aux_j+=1\n",
" \n",
" x = indexes\n",
" y = array_values\n",
" label = str(numP) + ' padres'\n",
" ax1.axvline(numP)\n",
" plt.plot(x, y, color=colors[i], label=label, marker=markers[1], markersize=10)\n",
" \n",
" ax1.axhline(1, color='k')\n",
" f.legend()\n",
" #f.tight_layout()\n",
" f.savefig(\"Images/\"+\"Iters\"+ dist_names[dist] +\"_SpeedUp\", format=\"png\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.4"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
%% Cell type:code id: tags:
``` python
%matplotlib inline
import pandas as pd
from pandas import DataFrame, Series
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from scipy import stats
import sys
```
%% Cell type:code id: tags:
``` python
matrixMalEX="data_GG.csv"
matrixMal="data_GM.csv"
matrixIt="data_L.csv"
n_qty=2 #CAMBIAR SEGUN LA CANTIDAD DE NODOS USADOS
repet = 3 * 2 #CAMBIAR EL PRIMER NUMERO SEGUN NUMERO DE EJECUCIONES POR CONFIG
p_value = 0.05
values = [2, 4, 8, 16, 32]
dist_names = ['null', 'BestFit', 'WorstFit']
```
%% Cell type:code id: tags:
``` python
def speedUp(arr, seq, df):
numP = df.loc[arr.index[0]].NP
return seq[( seq.NP == numP )]['EX'] / arr.mean()
```
%% Cell type:code id: tags:
``` python
dfG = pd.read_csv( matrixMalEX )
dfG = dfG.drop(columns=dfG.columns[0])
dfG['S'] = dfG['N']
dfG['N'] = dfG['S'] + dfG['%Async']
dfG['%Async'] = (dfG['%Async'] / dfG['N']) * 100
if(n_qty == 1):
group = dfG.groupby(['%Async', 'Groups'])['TE']
else:
group = dfG.groupby(['Dist', '%Async', 'Groups'])['TE']
#group
grouped_aggG = group.agg(['mean'])
grouped_aggG.rename(columns={'mean':'TE',}, inplace=True)
```
%% Cell type:code id: tags:
``` python
from natsort import index_natsorted
grouped_aggG.sort_values(
by="Groups",
key=lambda x: np.argsort(index_natsorted(df["Groups"]))
)
grouped_aggG
```
%% Cell type:code id: tags:
``` python
dfM = pd.read_csv( matrixMal )
dfM = dfM.drop(columns=dfM.columns[0])
dfM['S'] = dfM['N']
dfM['N'] = dfM['S'] + dfM['%Async']
dfM["TR"] = dfM["TC"] + dfM["TS"] + dfM["TA"]
dfM['%Async'] = (dfM['%Async'] / dfM['N']) * 100
if(n_qty == 1):
groupM = dfM.groupby(['%Async','NP', 'NS'])['TC', 'TS', 'TA', 'TR']
else:
groupM = dfM.groupby(['Dist', '%Async','NP', 'NS'])['TC', 'TS', 'TA', 'TR']
#group
grouped_aggM = groupM.agg(['mean'])
grouped_aggM.columns = grouped_aggM.columns.get_level_values(0)
```
%% Cell type:code id: tags:
``` python
dfL = pd.read_csv( matrixIt )
dfL = dfL.drop(columns=dfL.columns[0])
dfL['S'] = dfL['N']
dfL['N'] = dfL['S'] + dfL['%Async']
dfL['%Async'] = (dfL['%Async'] / dfL['N']) * 100
if(n_qty == 1):
groupL = dfL[dfL['NS'] != 0].groupby(['Tt', '%Async', 'NP', 'NS'])['Ti', 'To']
else:
groupL = dfL[dfL['NS'] != 0].groupby(['Tt', 'Dist', '%Async', 'NP', 'NS'])['Ti', 'To']
#group
grouped_aggL = groupL.agg(['mean', 'count'])
grouped_aggL.columns = grouped_aggL.columns.get_level_values(0)
grouped_aggL.set_axis(['Ti', 'Iters', 'To', 'Iters2'], axis='columns')
grouped_aggL['Iters'] = np.ceil(grouped_aggL['Iters']/6) # TODO Cambiar a repeticiones realizadas
grouped_aggL['Iters2'] = np.ceil(grouped_aggL['Iters2']/6)
```
%%%% Output: stream
/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:16: FutureWarning: set_axis currently defaults to operating inplace.
This will change in a future version of pandas, use inplace=True to avoid this warning.
app.launch_new_instance()
%% Cell type:code id: tags:
``` python
grouped_aggL.to_excel("resultL.xlsx")
grouped_aggM.to_excel("resultM.xlsx")
grouped_aggG.to_excel("resultG.xlsx")
```
%% Cell type:code id: tags:
``` python
dfG
```
%%%% Output: execute_result
N %Async Groups Dist Matrix Time Iters TE \
0 1000000000 0.0 8,32 2,2 100000 0.1 1000 31.525710
1 1000000000 0.0 8,32 2,2 100000 0.1 1000 33.315857
2 1000000000 0.0 8,32 2,2 100000 0.1 1000 33.347537
3 1000000000 75.0 4,32 1,1 100000 0.1 1000 57.219027
4 1000000000 75.0 4,32 1,1 100000 0.1 1000 55.469166
.. ... ... ... ... ... ... ... ...
595 1000000000 50.0 4,8 2,2 100000 0.1 1000 73.771357
596 1000000000 50.0 4,8 2,2 100000 0.1 1000 75.557508
597 1000000000 50.0 16,8 1,1 100000 0.1 1000 35.949913
598 1000000000 50.0 16,8 1,1 100000 0.1 1000 37.900348
599 1000000000 50.0 16,8 1,1 100000 0.1 1000 36.012772
S
0 1000000000
1 1000000000
2 1000000000
3 250000000
4 250000000
.. ...
595 500000000
596 500000000
597 500000000
598 500000000
599 500000000
[600 rows x 9 columns]
%% Cell type:code id: tags:
``` python
grouped_aggG
```
%%%% Output: execute_result
TE
Dist %Async Groups
1,1 0.0 16,2 112.087769
16,32 18.394706
16,4 61.887052
16,8 37.986463
2,16 113.187865
... ...
2,2 100.0 4,8 74.864688
8,16 37.999586
8,2 123.584176
8,32 31.730297
8,4 74.052463
[200 rows x 1 columns]
%% Cell type:code id: tags:
``` python
dfM
```
%%%% Output: execute_result
N %Async NP NS Dist Matrix Time Iters TC TS \
0 1000000000 0.0 8 32 2,2 100000 0.1 1000 1.341434 0.695848
1 1000000000 0.0 8 32 2,2 100000 0.1 1000 1.405911 0.639849
2 1000000000 0.0 8 32 2,2 100000 0.1 1000 1.346017 0.747863
3 1000000000 75.0 4 32 1,1 100000 0.1 1000 0.688062 0.064900
4 1000000000 75.0 4 32 1,1 100000 0.1 1000 0.702200 0.068039
.. ... ... .. .. ... ... ... ... ... ...
595 1000000000 50.0 4 8 2,2 100000 0.1 1000 0.390444 0.142287
596 1000000000 50.0 4 8 2,2 100000 0.1 1000 0.351690 0.154699
597 1000000000 50.0 16 8 1,1 100000 0.1 1000 0.337472 0.081077
598 1000000000 50.0 16 8 1,1 100000 0.1 1000 0.351262 0.103176
599 1000000000 50.0 16 8 1,1 100000 0.1 1000 0.349151 0.094121
TA S TR
0 0.000000 1000000000 2.037282
1 0.000000 1000000000 2.045760
2 0.000000 1000000000 2.093880
3 0.364941 250000000 1.117903
4 0.412072 250000000 1.182311
.. ... ... ...
595 0.338576 500000000 0.871307
596 0.354917 500000000 0.861306
597 0.243481 500000000 0.662030
598 0.261565 500000000 0.716003
599 0.221750 500000000 0.665022
[600 rows x 13 columns]
%% Cell type:code id: tags:
``` python
grouped_aggM
```
%%%% Output: execute_result
TC TS TA TR
Dist %Async NP NS
1,1 0.0 2 4 0.220380 0.314893 0.000000 0.535273
8 0.248143 0.375003 0.000000 0.623146
16 0.339042 0.486650 0.000000 0.825692
32 0.743840 0.506385 0.000000 1.250225
4 2 0.197858 0.311136 0.000000 0.508994
... ... ... ... ...
2,2 100.0 16 32 1.358891 0.000000 1.419016 2.777907
32 2 0.591882 0.000000 2.477300 3.069183
4 0.804965 0.000000 1.729638 2.534603
8 0.935128 0.000000 1.520682 2.455810
16 1.221248 0.000000 1.541904 2.763152
[200 rows x 4 columns]
%% Cell type:code id: tags:
``` python
dfL
```
%%%% Output: execute_result
N %Async NP N_par NS Dist Matrix Time Iters \
0 1000000000 0.0 32 8 0 2 100000 0.1 1000
1 1000000000 0.0 32 8 0 2 100000 0.1 1000
2 1000000000 0.0 32 8 0 2 100000 0.1 1000
3 1000000000 0.0 32 8 0 2 100000 0.1 1000
4 1000000000 0.0 32 8 0 2 100000 0.1 1000
... ... ... .. ... .. ... ... ... ...
1199995 1000000000 50.0 8 16 0 1 100000 0.1 1000
1199996 1000000000 50.0 8 16 0 1 100000 0.1 1000
1199997 1000000000 50.0 8 16 0 1 100000 0.1 1000
1199998 1000000000 50.0 8 16 0 1 100000 0.1 1000
1199999 1000000000 50.0 8 16 0 1 100000 0.1 1000
Ti Tt To S
0 0.005463 0.0 6.0 1000000000
1 0.005350 0.0 6.0 1000000000
2 0.005355 0.0 6.0 1000000000
3 0.005354 0.0 6.0 1000000000
4 0.005352 0.0 6.0 1000000000
... ... ... ... ...
1199995 0.024075 0.0 27.0 500000000
1199996 0.024076 0.0 27.0 500000000
1199997 0.024076 0.0 27.0 500000000
1199998 0.024411 0.0 27.0 500000000
1199999 0.024075 0.0 27.0 500000000
[1200000 rows x 13 columns]
%% Cell type:code id: tags:
``` python
grouped_aggL
```
%%%% Output: execute_result
Ti Iters To Iters2
Tt Dist %Async NP NS
0.0 1 0.0 2 4 0.099861 500.0 112.000000 500.0
8 0.099849 500.0 112.000000 500.0
16 0.099860 500.0 112.000000 500.0
32 0.099853 500.0 112.000000 500.0
4 2 0.049642 500.0 55.666667 500.0
... ... ... ... ...
1.0 2 100.0 16 32 0.034899 21.0 14.000000 21.0
32 2 0.012084 101.0 7.000000 101.0
4 0.015599 54.0 7.000000 54.0
8 0.012685 54.0 7.000000 54.0
16 0.015421 44.0 7.000000 44.0
[360 rows x 4 columns]
%% Cell type:code id: tags:
``` python
print("TIEMPO EJECUCCION")
for dist in [1,2]:
print("Distribución " + dist_names[dist] + " -------------------------")
dist_v = str(dist)+","+str(dist)
for numP in values:
print("Para ", numP, " padres")
for numC in values:
if numP != numC:
group = str(numP) + "," + str(numC)
v1 = dfG[(dfG["%Async"] == 0.0)][(dfG.Groups == group)][(dfG["Dist"] == dist_v)]['TE']
v2 = dfG[(dfG["%Async"] == 100.0)][(dfG.Groups == group)][(dfG["Dist"] == dist_v)]['TE']
res = stats.ttest_ind(v1, v2)
diff = grouped_aggG['TE'].loc[(dist_v, 0.0, group)] - grouped_aggG['TE'].loc[(dist_v, 100.0, group)]
if diff > 0:
mejor = "Asíncrono"
else:
mejor = "Síncrono"
if res[1] < p_value:
print("EX numC=", numC, "p =", round(res[1],3), "Diff =", abs(round(diff,3)), mejor)
```
%%%% Output: stream
TIEMPO EJECUCCION
Distribución BestFit -------------------------
Para 2 padres
EX numC= 4 p = 0.039 Diff = 0.09 Asíncrono
Para 4 padres
Para 8 padres
EX numC= 4 p = 0.0 Diff = 1.411 Síncrono
Para 16 padres
EX numC= 2 p = 0.014 Diff = 3.662 Asíncrono
Para 32 padres
EX numC= 4 p = 0.002 Diff = 4.589 Asíncrono
Distribución WorstFit -------------------------
Para 2 padres
Para 4 padres
EX numC= 16 p = 0.046 Diff = 0.304 Síncrono
EX numC= 32 p = 0.012 Diff = 0.542 Síncrono
Para 8 padres
%%%% Output: stream
/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:10: UserWarning: Boolean Series key will be reindexed to match DataFrame index.
# Remove the CWD from sys.path while we load stuff.
/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:11: UserWarning: Boolean Series key will be reindexed to match DataFrame index.
# This is added back by InteractiveShellApp.init_path()
%%%% Output: stream
Para 16 padres
EX numC= 2 p = 0.023 Diff = 6.349 Asíncrono
EX numC= 4 p = 0.0 Diff = 1.799 Asíncrono
EX numC= 8 p = 0.046 Diff = 0.261 Asíncrono
Para 32 padres
EX numC= 2 p = 0.01 Diff = 18.514 Asíncrono
EX numC= 4 p = 0.0 Diff = 4.454 Asíncrono
EX numC= 8 p = 0.003 Diff = 1.92 Asíncrono
%% Cell type:code id: tags:
``` python
print("TIEMPO MALLEABILITY")
for dist in [1,2]:
print("Distribución " + dist_names[dist] + " -------------------------")
dist_v = str(dist)+","+str(dist)
for numP in values:
print("Para ", numP, " padres")
for numC in values:
if numP != numC:
v1 = dfM[(dfM["%Async"] == 0.0)][(dfM.NP == numP)][(dfM.NS == numC)][(dfM["Dist"] == dist_v)]['TS']
v2 = dfM[(dfM["%Async"] == 100.0)][(dfM.NP == numP)][(dfM.NS == numC)][(dfM["Dist"] == dist_v)]['TA']
res = stats.ttest_ind(v1, v2)
diff = grouped_aggM['TS'].loc[(dist_v, 0.0, numP, numC)] - grouped_aggM['TA'].loc[(dist_v, 100.0, numP, numC)]
if diff > 0:
mejor = "Asíncrono"
else:
mejor = "Síncrono"
if res[1] < p_value:
print("TR numC=", numC, "p =", round(res[1],3), "Diff =", abs(round(diff,3)), mejor)
```
%%%% Output: stream
TIEMPO MALLEABILITY
Distribución 1 -------------------------
Para 2 padres
TR numC= 8 p = 0.0 Diff = 0.029 Síncrono
Para 4 padres
TR numC= 8 p = 0.006 Diff = 0.043 Síncrono
TR numC= 32 p = 0.025 Diff = 0.052 Síncrono
Para 8 padres
TR numC= 4 p = 0.016 Diff = 0.022 Asíncrono
TR numC= 16 p = 0.009 Diff = 0.031 Síncrono
TR numC= 32 p = 0.0 Diff = 0.483 Síncrono
Para 16 padres
TR numC= 8 p = 0.002 Diff = 0.072 Síncrono
TR numC= 32 p = 0.019 Diff = 0.549 Síncrono
Para 32 padres
TR numC= 4 p = 0.002 Diff = 0.981 Síncrono
TR numC= 8 p = 0.001 Diff = 0.972 Síncrono
TR numC= 16 p = 0.001 Diff = 0.831 Síncrono
Distribución 2 -------------------------
Para 2 padres
TR numC= 4 p = 0.0 Diff = 0.069 Síncrono
%%%% Output: stream
/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:9: UserWarning: Boolean Series key will be reindexed to match DataFrame index.
if __name__ == '__main__':
/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:10: UserWarning: Boolean Series key will be reindexed to match DataFrame index.
# Remove the CWD from sys.path while we load stuff.
%%%% Output: stream
TR numC= 8 p = 0.029 Diff = 0.035 Síncrono
Para 4 padres
TR numC= 8 p = 0.02 Diff = 0.031 Síncrono
TR numC= 32 p = 0.009 Diff = 0.601 Síncrono
Para 8 padres
TR numC= 16 p = 0.011 Diff = 0.729 Síncrono
TR numC= 32 p = 0.018 Diff = 0.423 Síncrono
Para 16 padres
TR numC= 4 p = 0.001 Diff = 0.884 Síncrono
TR numC= 32 p = 0.021 Diff = 0.36 Síncrono
Para 32 padres
TR numC= 2 p = 0.029 Diff = 1.548 Síncrono
TR numC= 4 p = 0.008 Diff = 0.84 Síncrono
TR numC= 8 p = 0.011 Diff = 0.726 Síncrono
TR numC= 16 p = 0.004 Diff = 0.606 Síncrono
%% Cell type:code id: tags:
``` python
print("TIEMPO Iters")
for dist in [1,2]:
print("Distribución " + dist_names[dist] + " -------------------------")
dist_v = str(dist)+","+str(dist)
for numP in values:
print("Para ", numP, " padres")
for numC in values:
if numP != numC:
#exp = dfL[(dfL["Tt"] == 0)][(dfL["Dist"] == 1)][(dfL["%Async"] == 0.0)][(dfL.NP == numP)][(dfL.NS == numC)]
#TimeOp = exp['Ti']
#print(TimeOp)
v1 = dfL[(dfL["Tt"] == 0)][(dfL["Dist"] == dist)][(dfL["%Async"] == 100.0)][(dfL.NP == numP)][(dfL.NS == numC)]['Ti']
v2 = dfL[(dfL["Tt"] == 1)][(dfL["Dist"] == dist)][(dfL["%Async"] == 100.0)][(dfL.NP == numP)][(dfL.NS == numC)]['Ti']
res = stats.ttest_ind(v1, v2, equal_var = False)
diff = grouped_aggL['Ti'].loc[(0, dist, 0.0, numP, numC)] - grouped_aggL['Ti'].loc[(1, dist, 100.0, numP, numC)]
if diff > 0:
mejor = "Asíncrono"
else:
mejor = "Síncrono"
if res[1] < p_value:
#and abs(diff) > grouped_aggL['Ti'].loc[(0, dist, 0.0, numP, numC)]
print("Ti numC=", numC, "p =", round(res[1],3), "Diff =", abs(round(diff,4)), mejor)
```
%%%% Output: stream
TIEMPO Iters
Distribución BestFit -------------------------
Para 2 padres
Ti numC= 4 p = 0.035 Diff = 0.0001 Síncrono
%%%% Output: stream
/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:12: UserWarning: Boolean Series key will be reindexed to match DataFrame index.
if sys.path[0] == '':
/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:13: UserWarning: Boolean Series key will be reindexed to match DataFrame index.
del sys.path[0]
%%%% Output: stream
Ti numC= 8 p = 0.025 Diff = 0.0001 Síncrono
Ti numC= 16 p = 0.002 Diff = 0.0001 Síncrono
Ti numC= 32 p = 0.007 Diff = 0.0001 Síncrono
Para 4 padres
Ti numC= 16 p = 0.0 Diff = 0.0006 Síncrono
Para 8 padres
Ti numC= 4 p = 0.0 Diff = 0.0009 Síncrono
Ti numC= 32 p = 0.007 Diff = 0.0029 Síncrono
Para 16 padres
Ti numC= 32 p = 0.0 Diff = 0.0097 Síncrono
Para 32 padres
Ti numC= 2 p = 0.0 Diff = 0.0029 Síncrono
Ti numC= 4 p = 0.0 Diff = 0.0056 Síncrono
Ti numC= 8 p = 0.0 Diff = 0.0055 Síncrono
Ti numC= 16 p = 0.0 Diff = 0.0053 Síncrono
Distribución WorstFit -------------------------
Para 2 padres
Ti numC= 32 p = 0.024 Diff = 0.0256 Síncrono
Para 4 padres
Ti numC= 16 p = 0.0 Diff = 0.0243 Síncrono
Ti numC= 32 p = 0.0 Diff = 0.0343 Síncrono
Para 8 padres
Ti numC= 4 p = 0.036 Diff = 0.0044 Síncrono
Ti numC= 16 p = 0.0 Diff = 0.008 Síncrono
Ti numC= 32 p = 0.0 Diff = 0.0225 Síncrono
Para 16 padres
Ti numC= 2 p = 0.0 Diff = 0.0067 Síncrono
Ti numC= 4 p = 0.0 Diff = 0.0092 Síncrono
Ti numC= 8 p = 0.0 Diff = 0.0098 Síncrono
Ti numC= 32 p = 0.0 Diff = 0.0224 Síncrono
Para 32 padres
Ti numC= 2 p = 0.0 Diff = 0.0058 Síncrono
Ti numC= 4 p = 0.0 Diff = 0.0094 Síncrono
Ti numC= 8 p = 0.0 Diff = 0.0064 Síncrono
Ti numC= 16 p = 0.0 Diff = 0.0092 Síncrono
%% Cell type:code id: tags:
``` python
iters = dfM['Iters'].mean()
resultados = [0,0]
for dist in [1,2]:
print("Distribución " + dist_names[dist] + " -------------------------")
dist_v = str(dist)+","+str(dist)
for numP in values:
print("Para ", numP, " padres")
for numC in values:
if numP != numC:
Titer = dfL[(dfL["Tt"] == 0)][(dfL["Dist"] == dist)][(dfL.NP == numC)]['Ti'].mean() #Tiempo por iteracion
i=0
for adr in [0.0, 100.0]:
auxExp = dfM[(dfM["Dist"] == dist_v)][(dfM["%Async"] == adr)][(dfM.NP == numP)][(dfM.NS == numC)]
Tr = auxExp['TS'].mean() + auxExp['TA'].mean() #Tiempo de redistribucion
M_it = dfL[(dfL["Tt"] == 1)][(dfL["Dist"] == dist)][(dfL["%Async"] == adr)][(dfL.NP == numP)][(dfL.NS == numC)]['Ti'].count()/3 #Iteraciones asincronas
#No se presupone una diferencia temporal entre iteraciones sincronas y asincronas
if(M_it > iters):
M_it = iters
resultados[i] = (iters - M_it) * Titer + Tr
i+=1
#print(M_it)
#print(Titer)
#print((iters - M_it) * Titer)
#print(Tr)
#print("End")
if resultados[0] > resultados[1]:
mejor = "Asíncrono"
else:
mejor = "Síncrono"
diff = abs(round(resultados[0] - resultados[1], 3))
print("NC="+ str(numC) + " Es mejor " + mejor + " con una diff de "+ str(diff))
#TODO Comprobar
```
%%%% Output: stream
Distribución BestFit -------------------------
Para 2 padres
NC=4 Es mejor Asíncrono con una diff de 0.123
%%%% Output: stream
/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:10: UserWarning: Boolean Series key will be reindexed to match DataFrame index.
# Remove the CWD from sys.path while we load stuff.
/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:14: UserWarning: Boolean Series key will be reindexed to match DataFrame index.
/home/usuario/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:16: UserWarning: Boolean Series key will be reindexed to match DataFrame index.
app.launch_new_instance()
%%%% Output: stream
NC=8 Es mejor Asíncrono con una diff de 0.07
NC=16 Es mejor Asíncrono con una diff de 0.046
NC=32 Es mejor Asíncrono con una diff de 0.014
Para 4 padres
NC=2 Es mejor Asíncrono con una diff de 0.645
NC=8 Es mejor Asíncrono con una diff de 0.13
NC=16 Es mejor Asíncrono con una diff de 0.052
NC=32 Es mejor Síncrono con una diff de 0.005
Para 8 padres
NC=2 Es mejor Asíncrono con una diff de 1.523
NC=4 Es mejor Asíncrono con una diff de 0.354
NC=16 Es mejor Asíncrono con una diff de 0.066
NC=32 Es mejor Síncrono con una diff de 0.327
Para 16 padres
NC=2 Es mejor Asíncrono con una diff de 3.676
NC=4 Es mejor Asíncrono con una diff de 1.235
NC=8 Es mejor Asíncrono con una diff de 0.406
NC=32 Es mejor Síncrono con una diff de 0.304
Para 32 padres
NC=2 Es mejor Asíncrono con una diff de 16.171
NC=4 Es mejor Asíncrono con una diff de 4.551
NC=8 Es mejor Asíncrono con una diff de 1.599
NC=16 Es mejor Asíncrono con una diff de 0.12
Distribución WorstFit -------------------------
Para 2 padres
NC=4 Es mejor Asíncrono con una diff de 0.13
NC=8 Es mejor Asíncrono con una diff de 0.064
NC=16 Es mejor Asíncrono con una diff de 0.034
NC=32 Es mejor Síncrono con una diff de 0.151
Para 4 padres
NC=2 Es mejor Asíncrono con una diff de 0.637
NC=8 Es mejor Asíncrono con una diff de 0.142
NC=16 Es mejor Síncrono con una diff de 0.28
NC=32 Es mejor Síncrono con una diff de 0.499
Para 8 padres
NC=2 Es mejor Asíncrono con una diff de 1.575
NC=4 Es mejor Asíncrono con una diff de 0.344
NC=16 Es mejor Síncrono con una diff de 0.32
NC=32 Es mejor Síncrono con una diff de 0.278
Para 16 padres
NC=2 Es mejor Asíncrono con una diff de 6.404
NC=4 Es mejor Asíncrono con una diff de 1.803
NC=8 Es mejor Asíncrono con una diff de 0.308
NC=32 Es mejor Síncrono con una diff de 0.113
Para 32 padres
NC=2 Es mejor Asíncrono con una diff de 18.438
NC=4 Es mejor Asíncrono con una diff de 4.533
NC=8 Es mejor Asíncrono con una diff de 1.927
NC=16 Es mejor Asíncrono con una diff de 0.481
%% Cell type:code id: tags:
``` python
grouped_aggL
```
%%%% Output: execute_result
Ti Iters To Iters2
Tt Dist %Async NP NS
0.0 1 0.0 2 4 0.099861 500.0 112.000000 500.0
8 0.099849 500.0 112.000000 500.0
16 0.099860 500.0 112.000000 500.0
32 0.099853 500.0 112.000000 500.0
4 2 0.049642 500.0 55.666667 500.0
... ... ... ... ...
1.0 2 100.0 16 32 0.034899 21.0 14.000000 21.0
32 2 0.012084 101.0 7.000000 101.0
4 0.015599 54.0 7.000000 54.0
8 0.012685 54.0 7.000000 54.0
16 0.015421 44.0 7.000000 44.0
[360 rows x 4 columns]
%% Cell type:code id: tags:
``` python
for dist in [1,2]:
v1 = grouped_aggL.loc[(0,dist,100.0,slice(None))]
v2 = grouped_aggL.loc[(1,dist,100.0,slice(None))]
aux_aggL = v1['Ti'].values / v2['Ti'].values
colors = ['r', 'orange', 'g', 'm', 'y']
markers = ['+', 'x', '1', '2', 'X']
f=plt.figure(figsize=(10, 7))
ax1 = f.add_subplot(111)
plt.xlim(0, max(values)+1)
plt.ylim(0, 1.2)
plt.xticks(values)
ax1.set_ylabel('Decremento velocidad')
ax1.set_xlabel('Procesos hijo')
ax1.set_title("Aumento de velocidad en las iteraciones al realizar redistribuciones asíncronas")
for i in range(len(values)):
numP = values[i]
c = colors[i]
mini = i * (len(values)-1)
maxi = (i+1) * (len(values)-1)
array_values = aux_aggL[mini:maxi]
indexes = np.arange(len(values)-1)
aux_j=0
for j in range(len(values)):
if(values[j] != numP):
indexes[aux_j] = values[j]
aux_j+=1
x = indexes
y = array_values
label = str(numP) + ' padres'
ax1.axvline(numP)
plt.plot(x, y, color=colors[i], label=label, marker=markers[1], markersize=10)
ax1.axhline(1, color='k')
f.legend()
#f.tight_layout()
f.savefig("Images/"+"Iters"+ dist_names[dist] +"_SpeedUp", format="png")
```
%%%% Output: display_data
[Hidden Image Output]
%%%% Output: display_data
[Hidden Image Output]
%% Cell type:code id: tags:
``` python
```
import sys
import glob
import numpy as np
import pandas as pd
def getData(lineS, outData, tp, hasIter = False):
for data in lineS:
k_v = data.split('=')
if k_v[0] == "time":
time = float(k_v[1])
elif k_v[0] == "iters" and hasIter:
iters = int(k_v[1])
outData[tp] = time
if hasIter:
outData[tp+1] = iters
#-----------------------------------------------
def record(f, observation, line):
# Record first line - General info
lineS = line.split()
for j in range(1,7):
observation[j] = int(lineS[j].split('=')[1])
# Record procces number
line = next(f)
lineS = line.split()
j = 7
for key_values in lineS:
k_v = key_values.split('=')
observation[j] = int(k_v[1])
j+=1
# Record data
j = 9
for j in range(9, 13):
line = next(f)
lineS = line.split()
getData(lineS, observation, j)
line = next(f)
lineS = line.split()
#if observation[0] == "A":
getData(lineS, observation, 13, True)
#else:
# getData(lineS, observation, 13)
#-----------------------------------------------
def read_file(f, dataA, dataB, it):
recording = False
resizes = 0
timer = 0
previousNP = 0
for line in f:
lineS = line.split()
if len(lineS) > 0:
if lineS[0] == "Config": # CONFIG LINE
recording = True
it += 1
dataA.append([None]*8)
dataB.append([None]*11)
resizes = int(lineS[2].split('=')[1].split(',')[0])
matrix = int(lineS[3].split('=')[1].split(',')[0])
sdr = int(lineS[4].split('=')[1].split(',')[0])
adr = int(lineS[5].split('=')[1].split(',')[0]) #TODO Que lo tome como porcentaje
time = float(lineS[7].split('=')[1])
dataB[it][5] = matrix
dataB[it][0] = sdr
dataB[it][1] = adr
dataB[it][6] = time
dataB[it][4] = ""
dataA[it][4] = matrix
dataA[it][0] = sdr
dataA[it][1] = adr
dataA[it][5] = time
dataA[it][3] = ""
elif recording and resizes != 0: # RESIZE LINE
iters = int(lineS[2].split('=')[1].split(',')[0])
npr = int(lineS[3].split('=')[1].split(',')[0])
dist = lineS[5].split('=')[1]
dataB[it][7] = iters
dataA[it][6] = iters
resizes = resizes - 1
if resizes == 0:
dataB[it][3] = npr
dataB[it][4] += dist
dataA[it][3] += dist
dataA[it][2] = str(previousNP) + "," + str(npr)
timer = 3
else:
dataB[it][2] = npr
dataB[it][4] += dist + ","
dataA[it][3] += dist + ","
previousNP = npr
else: # SAVE TIMES
if timer == 3:
dataB[it][8] = float(lineS[1])
elif timer == 2:
dataB[it][9] = float(lineS[1])
elif timer == 1:
dataB[it][10] = float(lineS[1])
else:
dataA[it][7] = float(lineS[1])
timer = timer - 1
return it
#columnsA1 = ["N", "%Async", "Groups", "Dist", "Matrix", "Time", "Iters", "TE"] #7
#columnsB1 = ["N", "%Async", "NP", "NS", "Dist", "Matrix", "Time", "Iters", "TC", "TS", "TA"] #10
#Config loaded: resizes=2, matrix=1000, sdr=1000000000, adr=0, aib=0, time=2.000000 || grp=1
#Resize 0: Iters=100, Procs=2, Factors=1.000000, Phy=2
#Resize 1: Iters=100, Procs=4, Factors=0.500000, Phy=2
#Tspawn: 0.249393
#Tsync: 0.330391
#Tasync: 0
#Tex: 301.428615
#-----------------------------------------------
if len(sys.argv) < 2:
print("The files name is missing\nUsage: python3 iterTimes.py resultsName directory csvOutName")
exit(1)
if len(sys.argv) >= 3:
BaseDir = sys.argv[2]
print("Searching in directory: "+ BaseDir)
else:
BaseDir = sys.argv[2]
if len(sys.argv) >= 4:
print("Csv name will be: " + sys.argv[3] + ".csv")
name = sys.argv[3]
else:
name = "data"
insideDir = "Run"
lista = glob.glob("./" + BaseDir + insideDir + "*/" + sys.argv[1]+ "*Global.o*")
print("Number of files found: "+ str(len(lista)));
it = -1
dataA = []
dataB = []
columnsA = ["N", "%Async", "Groups", "Dist", "Matrix", "Time", "Iters", "TE"] #7
columnsB = ["N", "%Async", "NP", "NS", "Dist", "Matrix", "Time", "Iters", "TC", "TS", "TA"] #10
for elem in lista:
f = open(elem, "r")
it = read_file(f, dataA, dataB, it)
f.close()
#print(data)
dfA = pd.DataFrame(dataA, columns=columnsA)
dfA.to_csv(name + '_G.csv')
dfB = pd.DataFrame(dataB, columns=columnsB)
dfB.to_csv(name + '_M.csv')
import sys
import glob
import numpy as np
import pandas as pd
#-----------------------------------------------
def read_file(f, data, it):
matrix = 0
sdr = 0
adr = 0
time = 0
recording = False
it_line = 0
aux_it = 0
iters = 0
np = 0
np_par = 0
ns = 0
for line in f:
lineS = line.split()
if len(lineS) > 1:
if recording:
aux_it = 0
if it_line==0:
lineS.pop(0)
for observation in lineS:
data.append([None]*11)
data[it+aux_it][0] = sdr
data[it+aux_it][1] = adr
data[it+aux_it][2] = np
data[it+aux_it][3] = np_par
data[it+aux_it][4] = ns
data[it+aux_it][5] = matrix
data[it+aux_it][6] = time
data[it+aux_it][7] = iters
data[it+aux_it][8] = float(observation)
aux_it+=1
it_line = it_line + 1
elif it_line==1:
lineS.pop(0)
for observation in lineS:
data[it+aux_it][9] = float(observation)
aux_it+=1
it_line = it_line + 1
else:
lineS.pop(0)
for observation in lineS:
data[it+aux_it][10] = float(observation)
aux_it+=1
it = it + aux_it
recording = False
it_line = 0
#TODO Que tome adr como porcentaje
if lineS[0] == "Config:":
matrix = int(lineS[1].split('=')[1].split(',')[0])
sdr = int(lineS[2].split('=')[1].split(',')[0])
adr = int(lineS[3].split('=')[1].split(',')[0])
time = float(lineS[5].split('=')[1])
elif lineS[0] == "Config":
recording = True
iters = int(lineS[2].split('=')[1].split(',')[0])
np = int(lineS[5].split('=')[1].split(',')[0])
np_par = int(lineS[6].split('=')[1].split(',')[0])
ns = int(float(lineS[7].split('=')[1]))
return it
#-----------------------------------------------
#Config: matrix=1000, sdr=1000000000, adr=0, aib=0 time=2.000000
#Config Group: iters=100, factor=1.000000, phy=2, procs=2, parents=0, sons=4
#Ttype: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
if len(sys.argv) < 2:
print("The files name is missing\nUsage: python3 iterTimes.py resultsName directory csvOutName")
exit(1)
if len(sys.argv) >= 3:
BaseDir = sys.argv[2]
print("Searching in directory: "+ BaseDir)
else:
BaseDir = sys.argv[2]
if len(sys.argv) >= 4:
print("Csv name will be: " + sys.argv[3] + ".csv")
name = sys.argv[3]
else:
name = "data"
insideDir = "Run"
lista = glob.glob("./" + BaseDir + insideDir + "*/" + sys.argv[1]+ "*ID*.o*")
print("Number of files found: "+ str(len(lista)));
it = 0
data = [] #0 #1 #2 #3 #4 #5 #6 #7 #8 #9 #10
columns = ["N", "%Async", "NP", "N_par", "NS", "Matrix", "Time", "Iters", "Ti", "Tt", "To"] #11
for elem in lista:
f = open(elem, "r")
it = read_file(f, data, it)
f.close()
#print(data)
df = pd.DataFrame(data, columns=columns)
df.to_csv(name + '.csv')
import sys
import glob
import numpy as np
import pandas as pd
def getData(lineS, outData, tp, hasIter = False):
for data in lineS:
k_v = data.split('=')
if k_v[0] == "time":
time = float(k_v[1])
elif k_v[0] == "iters" and hasIter:
iters = int(k_v[1])
outData[tp] = time
if hasIter:
outData[tp+1] = iters
#-----------------------------------------------
def record(f, observation, line):
# Record first line - General info
lineS = line.split()
for j in range(1,7):
observation[j] = int(lineS[j].split('=')[1])
# Record procces number
line = next(f)
lineS = line.split()
j = 7
for key_values in lineS:
k_v = key_values.split('=')
observation[j] = int(k_v[1])
j+=1
# Record data
j = 9
for j in range(9, 13):
line = next(f)
lineS = line.split()
getData(lineS, observation, j)
line = next(f)
lineS = line.split()
#if observation[0] == "A":
getData(lineS, observation, 13, True)
#else:
# getData(lineS, observation, 13)
#-----------------------------------------------
def read_file(f, dataA, dataB, it):
recording = False
resizes = 0
timer = 0
previousNP = 0
for line in f:
lineS = line.split()
if len(lineS) > 0:
if lineS[0] == "Config": # CONFIG LINE
recording = True
it += 1
dataA.append([None]*13)
dataB.append([None]*15)
#resizes = int(lineS[2].split('=')[1].split(',')[0])
resizes = 2
compute_tam = int(lineS[3].split('=')[1].split(',')[0])
comm_tam = int(lineS[4].split('=')[1].split(',')[0])
sdr = int(lineS[5].split('=')[1].split(',')[0])
adr = int(lineS[6].split('=')[1].split(',')[0]) #TODO Que lo tome como porcentaje
css = int(lineS[8].split('=')[1].split(',')[0])
cst = int(lineS[9].split('=')[1].split(',')[0])
# TODO Que obtenga Aib
time = float(lineS[10].split('=')[1])
dataB[it][0] = sdr
dataB[it][1] = adr
dataB[it][4] = ""
dataB[it][5] = compute_tam
dataB[it][6] = comm_tam
dataB[it][7] = cst
dataB[it][8] = css
dataB[it][9] = time
dataB[it][10] = ""
dataA[it][0] = sdr
dataA[it][1] = adr
dataA[it][5] = ""
dataA[it][6] = compute_tam
dataA[it][7] = comm_tam
dataA[it][8] = cst
dataA[it][9] = css
dataA[it][10] = time
dataA[it][11] = ""
elif recording and resizes != 0: # RESIZE LINE
iters = int(lineS[2].split('=')[1].split(',')[0])
npr = int(lineS[3].split('=')[1].split(',')[0])
dist = lineS[5].split('=')[1]
resizes = resizes - 1
if resizes == 0:
dataB[it][3] = npr
dataB[it][4] += dist
dataB[it][10] += str(iters)
dataA[it][4] = npr #FIXME No sera correcta si hay mas de una reconfig
dataA[it][2] = str(previousNP) + "," + str(npr)
dataA[it][5] += dist
dataA[it][11] += str(iters)
timer = 4
else:
dataB[it][2] = npr
dataB[it][4] += dist + ","
dataB[it][10] += str(iters) + ","
dataA[it][3] = npr
dataA[it][5] += dist + ","
dataA[it][11] += str(iters) + ","
previousNP = npr
else: # SAVE TIMES
if timer == 4:
dataB[it][11] = float(lineS[1])
elif timer == 3:
dataB[it][12] = float(lineS[1])
elif timer == 2:
dataB[it][13] = float(lineS[1])
elif timer == 1:
dataB[it][14] = float(lineS[1])
else:
dataA[it][12] = float(lineS[1])
timer = timer - 1
return it
#columnsA1 = ["N", "%Async", "Groups", "Dist", "Matrix", "CommTam", "Cst", "Css", "Time", "Iters", "TE"] #8
#columnsB1 = ["N", "%Async", "NP", "NS", "Dist", "Matrix", "CommTam", "Cst", "Css", "Time", "Iters", "TC", "TS", "TA"] #12
#Config loaded: resizes=2, matrix=1000, sdr=1000000000, adr=0, aib=0, time=2.000000 || grp=1
#Resize 0: Iters=100, Procs=2, Factors=1.000000, Phy=2
#Resize 1: Iters=100, Procs=4, Factors=0.500000, Phy=2
#Tspawn: 0.249393
#Tthread: 0
#Tsync: 0.330391
#Tasync: 0
#Tex: 301.428615
#Config loaded: resizes=1, matrix=0, comm_tam=0, sdr=0, adr=0, aib=0, cst=3, css=1, time=1 || grp=1
#-----------------------------------------------
if len(sys.argv) < 2:
print("The files name is missing\nUsage: python3 iterTimes.py resultsName directory csvOutName")
exit(1)
if len(sys.argv) >= 3:
BaseDir = sys.argv[2]
print("Searching in directory: "+ BaseDir)
else:
BaseDir = sys.argv[2]
if len(sys.argv) >= 4:
print("Csv name will be: " + sys.argv[3] + "G.csv & " + sys.argv[3] + "M.csv")
name = sys.argv[3]
else:
name = "data"
insideDir = "Run"
lista = glob.glob("./" + BaseDir + insideDir + "*/" + sys.argv[1]+ "*Global.o*")
print("Number of files found: "+ str(len(lista)));
it = -1
dataA = []
dataB = []
columnsA = ["N", "%Async", "Groups", "NP", "NS", "Dist", "Matrix", "CommTam", "Cst", "Css", "Time", "Iters", "TE"] #13
columnsB = ["N", "%Async", "NP", "NS", "Dist", "Matrix", "CommTam", "Cst", "Css", "Time", "Iters", "TC", "TH", "TS", "TA"] #15
for elem in lista:
f = open(elem, "r")
it = read_file(f, dataA, dataB, it)
f.close()
#print(data)
dfA = pd.DataFrame(dataA, columns=columnsA)
dfA.to_csv(name + 'G.csv')
dfB = pd.DataFrame(dataB, columns=columnsB)
#Poner en TC el valor real y en TH el necesario para la app
cond = dfB.TH != 0
dfB.loc[cond, ['TC', 'TH']] = dfB.loc[cond, ['TH', 'TC']].values
dfB.to_csv(name + 'M.csv')
Esta carpeta contiene códigos para poder analizar los resultados obtenidos.
Para utilizar los códigos es necesario Python con los módulos Numpy y Pandas.
El código analyser.ipynb necesita además de la aplicación JupyterLab.
Los códigos son los siguientes:
- Malltimes.py: Recoge los tiempos globales de maleabilidad y ejecución de todos los ficheros pasados como argumento y
los almacena en dos ficheros CSV para ser utilizados en analyser.ipynb
- Itertimes.py: Recoge los tiempos locales de iteraciones de un grupo de procesos de todos los ficheros pasados como
argumento y los almacena en un fichero CSV para ser utilizado en analyser.ipynb
+ Ejemplo de uso de ambos códigos (Esperan los mismos argumentos):
python3 Malltimes.py NombreFicheros DirectorioFicheros/ NombreCSV
NombreFicheros: La parte común de los ficheros, los códigos buscan solo aquellos nombres que empiecen por esta cadena.
Por defecto, con poner "R" es suficiente.
DirectorioFicheros/: Nombre del directorio donde se encuentran todos los resultados. Esta pensado para que busque
en todos las subdirectorios que tenga en el primer nivel, pero no en segundos niveles o más.
NombreCSV: Nombre del fichero CSV en el que escribir la recopilación de resultados.
- analyser.ipynb: Código para ser ejecutado por JupyterNotebook. Dentro del mismo hay que indicar los nombres de los
ficheros CSV a analizar y tras ellos ejecutar las celdas. Como resultado se obtienen tres ficheros XLSX e imagenes
en el directorio "Images", y además varios resultados sobre T-test entre varios resultados que se reflejan como
output en la salida estandar de JupyterNotebook.
This source diff could not be displayed because it is too large. You can view the blob instead.
import sys
import glob
import numpy as numpy
import pandas as pd
#-----------------------------------------------
def read_file(f, dataA, dataB, itA, itB):
compute_tam = 0
comm_tam = 0
sdr = 0
adr = 0
dist = 0
css = 0
cst = 0
time = 0
recording = False
it_line = 0
aux_itA = 0
aux_itB = 0
iters = 0
np = 0
np_par = 0
ns = 0
array = []
columnas = ['Titer','Ttype','Top']
#print(f)
for line in f:
lineS = line.split()
if len(lineS) > 1:
if recording and lineS[0].split(':')[0] in columnas: #Record data
aux_itA = 0
lineS.pop(0)
if it_line==0:
for observation in lineS:
dataA.append([None]*15)
dataA[itA+aux_itA][0] = sdr
dataA[itA+aux_itA][1] = adr
dataA[itA+aux_itA][2] = np
dataA[itA+aux_itA][3] = np_par
dataA[itA+aux_itA][4] = ns
dataA[itA+aux_itA][5] = dist
dataA[itA+aux_itA][6] = compute_tam
dataA[itA+aux_itA][7] = comm_tam
dataA[itA+aux_itA][8] = cst
dataA[itA+aux_itA][9] = css
dataA[itA+aux_itA][10] = time
dataA[itA+aux_itA][11] = iters
dataA[itA+aux_itA][12] = float(observation)
array.append(float(observation))
aux_itA+=1
elif it_line==1:
deleted = 0
for observation in lineS:
dataA[itA+aux_itA][13] = float(observation)
if float(observation) == 0:
array.pop(aux_itA - deleted)
deleted+=1
aux_itA+=1
else:
for observation in lineS:
dataA[itA+aux_itA][14] = float(observation)
aux_itA+=1
it_line += 1
if(it_line % 3 == 0): # Comprobar si se ha terminado de mirar esta ejecucion
recording = False
it_line = 0
itA = itA + aux_itA
if ns != 0: # Solo obtener datos de grupos con hijos
dataB.append([None]*14)
dataB[itB][0] = sdr
dataB[itB][1] = adr
dataB[itB][2] = np
dataB[itB][3] = np_par
dataB[itB][4] = ns
dataB[itB][5] = dist
dataB[itB][6] = compute_tam
dataB[itB][7] = comm_tam
dataB[itB][8] = cst
dataB[itB][9] = css
dataB[itB][10] = time
dataB[itB][11] = iters
dataB[itB][12] = tuple(array)
dataB[itB][13] = numpy.sum(array)
itB+=1
array = []
if lineS[0] == "Config:":
compute_tam = int(lineS[1].split('=')[1].split(',')[0])
comm_tam = int(lineS[2].split('=')[1].split(',')[0])
sdr = int(lineS[3].split('=')[1].split(',')[0])
adr = int(lineS[4].split('=')[1].split(',')[0])
css = int(lineS[6].split('=')[1].split(',')[0])
cst = int(lineS[7].split('=')[1].split(',')[0])
time = float(lineS[8].split('=')[1])
elif lineS[0] == "Config":
recording = True
iters = int(lineS[2].split('=')[1].split(',')[0])
dist = int(lineS[4].split('=')[1].split(',')[0])
np = int(lineS[5].split('=')[1].split(',')[0])
np_par = int(lineS[6].split('=')[1].split(',')[0])
ns = int(float(lineS[7].split('=')[1]))
return itA,itB
#-----------------------------------------------
#Config: matrix=1000, sdr=1000000000, adr=0, aib=0 time=2.000000
#Config Group: iters=100, factor=1.000000, phy=2, procs=2, parents=0, sons=4
#Ttype: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
if len(sys.argv) < 2:
print("The files name is missing\nUsage: python3 iterTimes.py resultsName directory csvOutName")
exit(1)
if len(sys.argv) >= 3:
BaseDir = sys.argv[2]
print("Searching in directory: "+ BaseDir)
else: #FIXME
BaseDir = sys.argv[2]
if len(sys.argv) >= 4:
print("Csv name will be: " + sys.argv[3] + ".csv and "+ sys.argv[3] + "_Total.csv")
name = sys.argv[3]
else:
name = "data"
insideDir = "Run"
lista = glob.glob("./" + BaseDir + insideDir + "*/" + sys.argv[1]+ "*ID*.o*")
print("Number of files found: "+ str(len(lista)));
itA = itB = 0
dataA = []
dataB = [] #0 #1 #2 #3 #4 #5 #6 #7 #8 #9 #10 #11 #12 #13 #14
columnsA = ["N", "%Async", "NP", "N_par", "NS", "Dist", "Compute_tam", "Comm_tam", "Cst", "Css","Time", "Iters", "Ti", "Tt", "To"] #15
columnsB = ["N", "%Async", "NP", "N_par", "NS", "Dist", "Compute_tam", "Comm_tam", "Cst", "Css","Time", "Iters", "Ti", "Sum"] #14
for elem in lista:
f = open(elem, "r")
itA,itB = read_file(f, dataA, dataB, itA, itB)
f.close()
#print(data)
dfA = pd.DataFrame(dataA, columns=columnsA)
dfB = pd.DataFrame(dataB, columns=columnsB)
dfA['N'] += dfA['%Async']
dfA['%Async'] = (dfA['%Async'] / dfA['N']) * 100
dfA.to_csv(name + '.csv')
dfB['N'] += dfB['%Async']
dfB['%Async'] = (dfB['%Async'] / dfB['N']) * 100
dfB.to_csv(name + '_Total.csv')
import sys
import glob
import numpy as numpy
import pandas as pd
if len(sys.argv) < 3:
print("The files name is missing\nUsage: python3 joinDf.py resultsName1.csv resultsName2.csv csvOutName")
exit(1)
if len(sys.argv) >= 4:
print("Csv name will be: " + sys.argv[3] + ".csv")
name = sys.argv[3]
else:
name = "dataJOINED"
df1 = pd.read_csv( sys.argv[1] )
df2 = pd.read_csv( sys.argv[2] )
frames = [df1, df2]
df3 = pd.concat(frames)
df3 = df3.drop(columns=df3.columns[0])
df3.to_csv(name + '.csv')
......@@ -33,12 +33,18 @@ static int handler(void* user, const char* section, const char* name,
} else if (MATCH("general", "matrix_tam")) {
pconfig->matrix_tam = atoi(value);
} else if (MATCH("general", "comm_tam")) {
pconfig->comm_tam = atoi(value);
} else if (MATCH("general", "SDR")) {
pconfig->sdr = atoi(value);
} else if (MATCH("general", "ADR")) {
pconfig->adr = atoi(value);
} else if (MATCH("general", "AIB")) {
} else if (MATCH("general", "AIB")) { //TODO Refactor cambiar nombre
pconfig->aib = atoi(value);
} else if (MATCH("general", "CST")) {
pconfig->cst = atoi(value);
} else if (MATCH("general", "CSS")) {
pconfig->css = atoi(value);
} else if (MATCH("general", "time")) {
pconfig->general_time = atof(value);
......@@ -134,8 +140,8 @@ void free_config(configuration *user_config) {
void print_config(configuration *user_config, int grp) {
if(user_config != NULL) {
int i;
printf("Config loaded: resizes=%d, matrix=%d, sdr=%d, adr=%d, aib=%d, time=%f || grp=%d\n",
user_config->resizes, user_config->matrix_tam, user_config->sdr, user_config->adr, user_config->aib, user_config->general_time, grp);
printf("Config loaded: resizes=%d, matrix=%d, comm_tam=%d, sdr=%d, adr=%d, aib=%d, css=%d, cst=%d, time=%f || grp=%d\n",
user_config->resizes, user_config->matrix_tam, user_config->comm_tam, user_config->sdr, user_config->adr, user_config->aib, user_config->css, user_config->cst, user_config->general_time, grp);
for(i=0; i<user_config->resizes; i++) {
printf("Resize %d: Iters=%d, Procs=%d, Factors=%f, Phy=%d\n",
i, user_config->iters[i], user_config->procs[i], user_config->factors[i], user_config->phy_dist[i]);
......@@ -159,8 +165,8 @@ void print_config_group(configuration *user_config, int grp) {
sons = user_config->procs[grp+1];
}
printf("Config: matrix=%d, sdr=%d, adr=%d, aib=%d time=%f\n",
user_config->matrix_tam, user_config->sdr, user_config->adr, user_config->aib, user_config->general_time);
printf("Config: matrix=%d, comm_tam=%d, sdr=%d, adr=%d, aib=%d, css=%d, cst=%d, time=%f\n",
user_config->matrix_tam, user_config->comm_tam, user_config->sdr, user_config->adr, user_config->aib, user_config->css, user_config->cst, user_config->general_time);
printf("Config Group: iters=%d, factor=%f, phy=%d, procs=%d, parents=%d, sons=%d\n",
user_config->iters[grp], user_config->factors[grp], user_config->phy_dist[grp], user_config->procs[grp], parents, sons);
}
......@@ -245,15 +251,15 @@ configuration *recv_config_file(int root, MPI_Comm intercomm) {
* de la estructura de configuracion con una sola comunicacion.
*/
void def_struct_config_file(configuration *config_file, MPI_Datatype *config_type) {
int i, counts = 8;
int blocklengths[8] = {1, 1, 1, 1, 1, 1, 1, 1};
int i, counts = 11;
int blocklengths[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
MPI_Aint displs[counts], dir;
MPI_Datatype types[counts];
// Rellenar vector types
types[0] = types[1] = types[2] = types[3] = types[4] = types[5] = MPI_INT;
types[6] = MPI_FLOAT;
types[7] = MPI_DOUBLE;
types[0] = types[1] = types[2] = types[3] = types[4] = types[5] = types[6] = types[7] = types[8] = MPI_INT;
types[9] = MPI_FLOAT;
types[10] = MPI_DOUBLE;
// Rellenar vector displs
MPI_Get_address(config_file, &dir);
......@@ -261,11 +267,14 @@ void def_struct_config_file(configuration *config_file, MPI_Datatype *config_typ
MPI_Get_address(&(config_file->resizes), &displs[0]);
MPI_Get_address(&(config_file->actual_resize), &displs[1]);
MPI_Get_address(&(config_file->matrix_tam), &displs[2]);
MPI_Get_address(&(config_file->sdr), &displs[3]);
MPI_Get_address(&(config_file->adr), &displs[4]);
MPI_Get_address(&(config_file->aib), &displs[5]);
MPI_Get_address(&(config_file->general_time), &displs[6]);
MPI_Get_address(&(config_file->Top), &displs[7]);
MPI_Get_address(&(config_file->comm_tam), &displs[3]);
MPI_Get_address(&(config_file->sdr), &displs[4]);
MPI_Get_address(&(config_file->adr), &displs[5]);
MPI_Get_address(&(config_file->aib), &displs[6]);
MPI_Get_address(&(config_file->css), &displs[7]);
MPI_Get_address(&(config_file->cst), &displs[8]);
MPI_Get_address(&(config_file->general_time), &displs[9]);
MPI_Get_address(&(config_file->Top), &displs[10]);
for(i=0;i<counts;i++) displs[i] -= dir;
......
......@@ -7,7 +7,8 @@ typedef struct
{
int resizes;
int actual_resize;
int matrix_tam, sdr, adr;
int matrix_tam, comm_tam, sdr, adr;
int css, cst;
int aib;
float general_time;
double Top;
......
......@@ -11,7 +11,7 @@ void def_results_type(results_data *results, int resizes, MPI_Datatype *results_
//======================================================||
//======================================================||
//TODO Generalizar ambas funciones en una sola
/*
* Envia una estructura de resultados al grupo de procesos al que se
* enlaza este grupo a traves del intercomunicador pasado como argumento.
......@@ -57,17 +57,17 @@ void recv_results(results_data *results, int root, int resizes, MPI_Comm interco
* Define un tipo derivado de MPI para mandar los tiempos
* con una sola comunicacion.
*
* En concreto son tres escales y un vector de tamaño "resizes"
* En concreto son tres escalares y dos vectores de tamaño "resizes"
*/
void def_results_type(results_data *results, int resizes, MPI_Datatype *results_type) {
int i, counts = 4;
int blocklengths[4] = {1, 1, 1, 1};
int i, counts = 5;
int blocklengths[] = {1, 1, 1, 1, 1};
MPI_Aint displs[counts], dir;
MPI_Datatype types[counts];
// Rellenar vector types
types[0] = types[1] = types[2] = types[3] = MPI_DOUBLE;
blocklengths[3] = resizes;
types[0] = types[1] = types[2] = types[3] = types[4] = MPI_DOUBLE;
blocklengths[3] = blocklengths[4] = resizes;
// Rellenar vector displs
MPI_Get_address(results, &dir);
......@@ -75,13 +75,65 @@ void def_results_type(results_data *results, int resizes, MPI_Datatype *results_
MPI_Get_address(&(results->sync_start), &displs[0]);
MPI_Get_address(&(results->async_start), &displs[1]);
MPI_Get_address(&(results->exec_start), &displs[2]);
MPI_Get_address(&(results->spawn_time[0]), &displs[3]); //TODO Revisar si se puede simplificar
MPI_Get_address(&(results->spawn_real_time[0]), &displs[3]);
MPI_Get_address(&(results->spawn_time[0]), &displs[4]); //TODO Revisar si se puede simplificar //FIXME Si hay mas de un spawn error?
for(i=0;i<counts;i++) displs[i] -= dir;
MPI_Type_create_struct(counts, blocklengths, displs, types, results_type);
MPI_Type_commit(results_type);
}
//======================================================||
//======================================================||
//================SET RESULTS FUNCTIONS=================||
//======================================================||
//======================================================||
/*
* Guarda los resultados respecto a la redistribución de datos
* tras una reconfiguración. A llamar por los hijos tras
* terminar la redistribución y obtener la configuración.
*/
void set_results_post_reconfig(results_data *results, int grp, int sdr, int adr) {
if(sdr) { // Si no hay datos sincronos, el tiempo es 0
results->sync_time[grp] = results->sync_end - results->sync_start;
} else {
results->sync_time[grp] = 0;
}
if(adr) { // Si no hay datos asincronos, el tiempo es 0
results->async_time[grp] = results->async_end - results->async_start;
} else {
results->async_time[grp] = 0;
}
}
/*
* Pone el indice del siguiente elemento a escribir a 0 para los vectores
* que tengan que ver con las iteraciones.
* Por tanto, todos los anteriores valores de esos vectores pasan a ser invalidos
* si se intentan acceder desde un código externo.
*
* Solo es necesario llamar a esta funcion cuando se ha realizado una
* expansion con el metodo MERGE
*/
void reset_results_index(results_data *results) {
results->iter_index = 0;
}
/*
* Obtiene para cada iteracion, el tiempo maximo entre todos los procesos
* que han participado.
*
* Es necesario obtener el maximo, pues es el que representa el tiempo real
* que se ha utilizado.
*/
void compute_results_iter(results_data *results, int myId, int root, MPI_Comm comm) {
if(myId == root)
MPI_Reduce(MPI_IN_PLACE, results->iters_time, results->iter_index, MPI_DOUBLE, MPI_MAX, root, comm);
else
MPI_Reduce(results->iters_time, NULL, results->iter_index, MPI_DOUBLE, MPI_MAX, root, comm);
}
//======================================================||
//======================================================||
......@@ -95,22 +147,22 @@ void def_results_type(results_data *results, int resizes, MPI_Datatype *results_
* por iteracion, el tipo (Normal o durante communicacion asincrona)
* y cuantas operaciones internas se han realizado en cada iteracion.
*/
void print_iter_results(results_data *results, int last_normal_iter_index) {
void print_iter_results(results_data results, int last_normal_iter_index) {
int i, aux;
printf("Titer: ");
for(i=0; i< results->iter_index; i++) {
printf("%lf ", results->iters_time[i]);
for(i=0; i< results.iter_index; i++) {
printf("%lf ", results.iters_time[i]);
}
printf("\nTtype: "); //FIXME modificar a imprimir solo la cantidad de asincronas
for(i=0; i< results->iter_index; i++) {
printf("%d ", results->iters_type[i] == 0);
for(i=0; i< results.iter_index; i++) {
printf("%d ", results.iters_type[i] == 0);
}
printf("\nTop: "); //FIXME modificar a imprimir solo cuantas operaciones cuestan una iteracion
for(i=0; i< results->iter_index; i++) {
aux = results->iters_type[i] == 0 ? results->iters_type[last_normal_iter_index] : results->iters_type[i];
printf("\nTop: "); //TODO modificar a imprimir solo cuantas operaciones cuestan una iteracion?
for(i=0; i< results.iter_index; i++) {
aux = results.iters_type[i] == 0 ? results.iters_type[last_normal_iter_index] : results.iters_type[i];
printf("%d ", aux);
}
printf("\n");
......@@ -121,25 +173,30 @@ void print_iter_results(results_data *results, int last_normal_iter_index) {
* Estos son el tiempo de creacion de procesos, los de comunicacion
* asincrona y sincrona y el tiempo total de ejecucion.
*/
void print_global_results(results_data *results, int resizes) {
void print_global_results(results_data results, int resizes) {
int i;
printf("Tspawn: ");
printf("Tspawn: "); // FIXME REFACTOR Cambiar nombre a T_resize_real
for(i=0; i< resizes - 1; i++) {
printf("%lf ", results->spawn_time[i]);
printf("%lf ", results.spawn_time[i]);
}
printf("\nTspawn_real: "); // FIXME REFACTOR Cambiar nombre a T_resize
for(i=0; i< resizes - 1; i++) {
printf("%lf ", results.spawn_real_time[i]);
}
printf("\nTsync: ");
for(i=1; i < resizes; i++) {
printf("%lf ", results->sync_time[i]);
printf("%lf ", results.sync_time[i]);
}
printf("\nTasync: ");
for(i=1; i < resizes; i++) {
printf("%lf ", results->async_time[i]);
printf("%lf ", results.async_time[i]);
}
printf("\nTex: %lf\n", results->exec_time);
printf("\nTex: %lf\n", results.exec_time);
}
//======================================================||
......@@ -154,27 +211,50 @@ void print_global_results(results_data *results, int resizes) {
* Los argumentos "resizes" y "iters_size" se necesitan para obtener el tamaño
* de los vectores de resultados.
*/
void init_results_data(results_data **results, int resizes, int iters_size) {
*results = malloc(1 * sizeof(results_data));
void init_results_data(results_data *results, int resizes, int iters_size) {
//*results = malloc(1 * sizeof(results_data)); FIXME Borrar
results->spawn_time = calloc(resizes, sizeof(double));
results->spawn_real_time = calloc(resizes, sizeof(double));
results->sync_time = calloc(resizes, sizeof(double));
results->async_time = calloc(resizes, sizeof(double));
(*results)->spawn_time = calloc(resizes, sizeof(double));
(*results)->sync_time = calloc(resizes, sizeof(double));
(*results)->async_time = calloc(resizes, sizeof(double));
results->iters_size = iters_size + 100;
results->iters_time = calloc(iters_size + 100, sizeof(double)); //FIXME Numero magico
results->iters_type = calloc(iters_size + 100, sizeof(int));
results->iter_index = 0;
(*results)->iters_time = calloc(iters_size * 20, sizeof(double)); //FIXME Numero magico - Añadir funcion que amplie tamaño
(*results)->iters_type = calloc(iters_size * 20, sizeof(int));
(*results)->iter_index = 0;
}
void realloc_results_iters(results_data *results, int needed) {
double *time_aux;
int *type_aux;
time_aux = (double *) realloc(results->iters_time, needed * sizeof(double));
type_aux = (int *) realloc(results->iters_type, needed * sizeof(int));
if(time_aux == NULL || type_aux == NULL) {
fprintf(stderr, "Fatal error - No se ha podido realojar la memoria de resultados\n");
MPI_Abort(MPI_COMM_WORLD, 1);
}
results->iters_time = time_aux;
results->iters_type = type_aux;
}
/*
* Libera toda la memoria asociada con una estructura de resultados.
* TODO Asegurar que ha sido inicializado?
*/
void free_results_data(results_data **results) {
free((*results)->spawn_time);
free((*results)->sync_time);
free((*results)->async_time);
free((*results)->iters_time);
free((*results)->iters_type);
free(*results);
void free_results_data(results_data *results) {
if(results != NULL) {
free(results->spawn_time);
free(results->spawn_real_time);
free(results->sync_time);
free(results->async_time);
free(results->iters_time);
free(results->iters_type);
}
//free(*results); FIXME Borrar
}
......@@ -2,23 +2,31 @@
#include <stdlib.h>
#include <mpi.h>
#define RESULTS_INIT_DATA_QTY 100
typedef struct {
// Iters data
double *iters_time;
int *iters_type, iter_index;
int *iters_type, iter_index, iters_size;
// Spawn, Sync and Async time
double spawn_start, *spawn_time;
double sync_start, *sync_time;
double async_start, *async_time;
// Spawn, Thread, Sync, Async and Exec time
double spawn_start, *spawn_time, *spawn_real_time;
double sync_start, sync_end, *sync_time;
double async_start, async_end, *async_time;
double exec_start, exec_time;
//Overcharge time is time spent in malleability that is from IO modules
} results_data;
void send_results(results_data *results, int root, int resizes, MPI_Comm intercomm);
void recv_results(results_data *results, int root, int resizes, MPI_Comm intercomm);
void print_iter_results(results_data *results, int last_normal_iter_index);
void print_global_results(results_data *results, int resizes);
void init_results_data(results_data **results, int resizes, int iters_size);
void free_results_data(results_data **results);
void set_results_post_reconfig(results_data *results, int grp, int sdr, int adr);
void reset_results_index(results_data *results);
void compute_results_iter(results_data *results, int myId, int root, MPI_Comm comm);
void print_iter_results(results_data results, int last_normal_iter_index);
void print_global_results(results_data results, int resizes);
void init_results_data(results_data *results, int resizes, int iters_size);
void realloc_results_iters(results_data *results, int needed);
void free_results_data(results_data *results);
......@@ -2,36 +2,25 @@
#include <stdlib.h>
#include <mpi.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <pthread.h>
#include "computing_func.h"
#include "../IOcodes/read_ini.h"
#include "../IOcodes/results.h"
#include "../malleability/ProcessDist.h"
#include "../malleability/CommDist.h"
#include "../malleability/malleabilityManager.h"
#include "../malleability/malleabilityStates.h"
#define ROOT 0
int work();
void Sons_init();
int checkpoint(int iter, int state, MPI_Request **comm_req);
void TC(int numS);
int start_redistribution(int numS, MPI_Request **comm_req);
int check_redistribution(int iter, MPI_Request **comm_req);
int end_redistribution(int iter);
int thread_creation();
int thread_check();
void* thread_async_work(void* void_arg);
void iterate(double *matrix, int n, int async_comm);
void iterate(double *matrix, int n, int async_comm, int iter);
void init_group_struct(char *argv[], int argc, int myId, int numP);
void init_application();
void obtain_op_times();
void free_application_data();
void print_general_info(int myId, int grp, int numP);
int print_local_results();
int print_final_results();
int create_out_file(char *nombre, int *ptr, int newstdout);
......@@ -43,58 +32,152 @@ typedef struct {
int argc;
int numS; // Cantidad de procesos hijos
int commAsync;
MPI_Comm children, parents;
char *compute_comm_array;
char **argv;
char *sync_array, *async_array;
} group_data;
typedef struct {
int myId, numP, numS, adr;
MPI_Comm children;
char *sync_array;
} thread_data;
configuration *config_file;
group_data *group;
results_data *results;
MPI_Comm comm;
int run_id = 0; // Utilizado para diferenciar más fácilmente ejecuciones en el análisis
pthread_t async_thread; // TODO Cambiar de sitio?
int main(int argc, char *argv[]) {
int numP, myId, res;
int req;
int im_child;
MPI_Init_thread(&argc, &argv, MPI_THREAD_SINGLE, &req);
//FIXME El codigo no es capaz de hacer mas de una redistribucion - Arreglar malleabilityTypes.c
int num_cpus, num_nodes; //nodelist_len; //FIXME Eliminar cuando se utilice Slurm
char *nodelist = NULL;
num_cpus = 20; //FIXME NUMERO MAGICO
if (argc >= 5) {
nodelist = argv[3];
//nodelist_len = strlen(nodelist);
num_nodes = atoi(argv[4]);
num_cpus = num_nodes * num_cpus;
}
MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &req);
MPI_Comm_size(MPI_COMM_WORLD, &numP);
MPI_Comm_rank(MPI_COMM_WORLD, &myId);
comm = MPI_COMM_WORLD;
if(req != MPI_THREAD_MULTIPLE) {
printf("No se ha obtenido la configuración de hilos necesaria\nSolicitada %d -- Devuelta %d\n", req, MPI_THREAD_MULTIPLE);
}
init_group_struct(argv, argc, myId, numP);
//FIXME No funciona en OpenMPI
im_child = init_malleability(myId, numP, ROOT, comm, argv[0], nodelist, num_cpus, num_nodes);
MPI_Comm_get_parent(&(group->parents));
if(group->parents == MPI_COMM_NULL ) { // Si son el primer grupo de procesos, recogen la configuracion inicial
if(!im_child) { //TODO REFACTOR Simplificar inicio
init_application();
} else { // Si son procesos hijos deben comunicarse con las padres
Sons_init();
set_benchmark_grp(group->grp);
set_benchmark_configuration(config_file);
set_benchmark_results(results);
MPI_Barrier(comm);
results->exec_start = MPI_Wtime();
} else { //Init hijos
get_malleability_user_comm(&comm);
get_benchmark_configuration(&config_file);
get_benchmark_results(&results);
set_results_post_reconfig(results, group->grp, config_file->sdr, config_file->adr); //TODO Cambio al añadir nueva redistribucion
if(config_file->comm_tam) {
group->compute_comm_array = malloc(config_file->comm_tam * sizeof(char));
}
// TODO Refactor - Que sea una unica funcion
// Obtiene las variables que van a utilizar los hijos
void *value = NULL;
malleability_get_data(&value, 0, 1, 1);
group->grp = *((int *)value);
free(value);
malleability_get_data(&value, 1, 1, 1);
run_id = *((int *)value);
free(value);
malleability_get_data(&value, 2, 1, 1);
group->iter_start = *((int *)value);
free(value);
//FIXME Eliminar cuando se utilice SLURM
/*
malleability_get_data(&value, 4, 1, 1);
num_nodes = *((int *)value);
free(value);
malleability_get_data(&value, 5, 1, 1);
nodelist = (char *)value;
//free(value);
nodelist_len = strlen(nodelist);
*/
group->grp = group->grp + 1;
}
//
// EMPIEZA LA EJECUCION-------------------------------
//
group->grp = group->grp - 1; // TODO REFACTOR???
do {
group->grp = group->grp + 1;
set_benchmark_grp(group->grp);
get_malleability_user_comm(&comm);
MPI_Comm_size(comm, &(group->numP));
MPI_Comm_rank(comm, &(group->myId));
if(config_file->resizes != group->grp + 1) {
set_malleability_configuration(config_file->cst, config_file->css, config_file->phy_dist[group->grp+1], -1, config_file->aib, -1);
set_children_number(config_file->procs[group->grp+1]); // TODO TO BE DEPRECATED
if(group->grp == 0) {
MPI_Barrier(MPI_COMM_WORLD);
results->exec_start = MPI_Wtime();
malleability_add_data(&(group->grp), 1, MAL_INT, 1, 1);
malleability_add_data(&run_id, 1, MAL_INT, 1, 1);
malleability_add_data(&(group->iter_start), 1, MAL_INT, 1, 1);
//FIXME Eliminar cuando se utilice SLURM
//malleability_add_data(&num_nodes, 1, MAL_INT, 1, 1);
//malleability_add_data(&nodelist, nodelist_len, MAL_CHAR, 1, 1);
}
}
res = work();
if(res == MAL_ZOMBIE) break;
print_local_results();
reset_results_index(results);
} while((config_file->resizes > group->grp + 1) && (config_file->cst == COMM_SPAWN_MERGE || config_file->cst == COMM_SPAWN_MERGE_PTHREAD));
//
// TERMINA LA EJECUCION ----------------------------------------------------------
//
if(res) { // Se he llegado al final de la aplicacion
MPI_Barrier(MPI_COMM_WORLD);
if(res==1) { // Se he llegado al final de la aplicacion
MPI_Barrier(comm); // TODO Posible error al utilizar SHRINK
results->exec_time = MPI_Wtime() - results->exec_start;
}
print_final_results(); // Pasado este punto ya no pueden escribir los procesos
print_final_results();
if(comm != MPI_COMM_WORLD && comm != MPI_COMM_NULL) {
MPI_Comm_free(&comm);
}
if(group->myId == ROOT && (config_file->cst == COMM_SPAWN_MERGE || config_file->cst == COMM_SPAWN_MERGE_PTHREAD)) {
MPI_Abort(MPI_COMM_WORLD, -100);
}
free_application_data();
MPI_Finalize();
return 0;
}
......@@ -115,274 +198,35 @@ int main(int argc, char *argv[]) {
*/
int work() {
int iter, maxiter, state, res;
double *matrix;
MPI_Request *async_comm;
double *matrix = NULL;
maxiter = config_file->iters[group->grp];
//initMatrix(&matrix, config_file->matrix_tam);
state = MAL_COMM_UNINITIALIZED;
state = MAL_NOT_STARTED;
res = 0;
for(iter=group->iter_start; iter < maxiter; iter++) {
iterate(matrix, config_file->matrix_tam, state);
iterate(matrix, config_file->matrix_tam, state, iter);
}
state = checkpoint(iter, state, &async_comm);
if(config_file->resizes != group->grp + 1)
state = malleability_checkpoint();
iter = 0;
while(state == MAL_ASYNC_PENDING) {
iterate(matrix, config_file->matrix_tam, state);
while(state == MAL_DIST_PENDING || state == MAL_SPAWN_PENDING || state == MAL_SPAWN_SINGLE_PENDING) {
if(iter < config_file->iters[group->grp+1]) {
iterate(matrix, config_file->matrix_tam, state, iter);
iter++;
state = checkpoint(iter, state, &async_comm);
group->iter_start = iter;
}
state = malleability_checkpoint();
}
if(config_file->resizes - 1 == group->grp) res=1;
if(state == MAL_ZOMBIE) res=state;
return res;
}
/*
* Se realiza el redimensionado de procesos por parte de los padres.
*
* Se crean los nuevos procesos con la distribucion fisica elegida y
* a continuacion se transmite la informacion a los mismos.
*
* Si hay datos asincronos a transmitir, primero se comienza a
* transmitir estos y se termina la funcion. Se tiene que comprobar con
* llamando a la función de nuevo que se han terminado de enviar
*
* Si hay ademas datos sincronos a enviar, no se envian aun.
*
* Si solo hay datos sincronos se envian tras la creacion de los procesos
* y finalmente se desconectan los dos grupos de procesos.
*/
int checkpoint(int iter, int state, MPI_Request **comm_req) {
if(state == MAL_COMM_UNINITIALIZED) {
// Comprobar si se tiene que realizar un redimensionado
if(config_file->iters[group->grp] > iter || config_file->resizes == group->grp + 1) {return MAL_COMM_UNINITIALIZED;}
group->numS = config_file->procs[group->grp +1];
results->spawn_start = MPI_Wtime();
TC(group->numS);
results->spawn_time[group->grp] = MPI_Wtime() - results->spawn_start;
state = start_redistribution(group->numS, comm_req);
} else if(state == MAL_ASYNC_PENDING) {
state = thread_check();
if(state == MAL_COMM_COMPLETED) end_redistribution(iter);
//state = check_redistribution(iter, comm_req);
}
return state;
}
/*
* Se encarga de realizar la creacion de los procesos hijos.
*/
void TC(int numS){
// Inicialización de la comunicación con SLURM
int dist = config_file->phy_dist[group->grp +1];
init_slurm_comm(group->argv, group->myId, numS, ROOT, dist, COMM_SPAWN_SERIAL);
// Esperar a que la comunicación y creación de procesos
// haya finalizado
int test = -1;
while(test != MPI_SUCCESS) {
test = check_slurm_comm(group->myId, ROOT, MPI_COMM_WORLD, &(group->children));
}
}
/*
* Comienza la redistribucion de los datos con el nuevo grupo de procesos.
*
* Primero se envia la configuracion a utilizar al nuevo grupo de procesos y a continuacion
* se realiza el envio asincrono y/o sincrono si lo hay.
*
* En caso de que haya comunicacion asincrona, se comienza y se termina la funcion
* indicando que se ha comenzado un envio asincrono.
*
* Si no hay comunicacion asincrono se pasa a realizar la sincrona si la hubiese.
*
* Finalmente se envian datos sobre los resultados a los hijos y se desconectan ambos
* grupos de procesos.
*/
int start_redistribution(int numS, MPI_Request **comm_req) {
int rootBcast = MPI_PROC_NULL;
if(group->myId == ROOT) rootBcast = MPI_ROOT;
// Enviar a los hijos que grupo de procesos son
MPI_Bcast(&(group->grp), 1, MPI_INT, rootBcast, group->children);
MPI_Bcast(&run_id, 1, MPI_INT, rootBcast, group->children);
send_config_file(config_file, rootBcast, group->children);
if(config_file->adr > 0) {
results->async_start = MPI_Wtime();
return thread_creation();
}
return end_redistribution(0);
}
/*
* Crea una hebra para ejecutar una comunicación en segundo plano.
*/
int thread_creation() {
if(pthread_create(&async_thread, NULL, thread_async_work, NULL)) {
printf("Error al crear el hilo\n");
MPI_Abort(MPI_COMM_WORLD, -1);
return -1;
}
return MAL_ASYNC_PENDING;
}
/*
* Comprobación por parte de una hebra maestra que indica
* si una hebra esclava ha terminado su comunicación en segundo plano.
*
* El estado de la comunicación es devuelto al finalizar la función.
*/
int thread_check() {
if(group->commAsync == MAL_COMM_COMPLETED) {
if(pthread_join(async_thread, NULL)) {
printf("Error al esperar al hilo\n");
MPI_Abort(MPI_COMM_WORLD, -1);
return -2;
}
return MAL_COMM_COMPLETED;
}
return MAL_ASYNC_PENDING;
}
/*
* Función ejecutada por una hebra.
* Ejecuta una comunicación síncrona con los hijos que
* para el usuario se puede considerar como en segundo plano.
*
* Cuando termina la comunicación la hebra maestra puede comprobarlo
* por el valor "commAsync".
*/
void* thread_async_work(void* void_arg) {
send_sync(group->async_array, config_file->adr, group->myId, group->numP, ROOT, group->children, group->numS);
group->commAsync = MAL_COMM_COMPLETED;
pthread_exit(NULL);
}
/*
* @deprecated
* Comprueba si la redistribucion asincrona ha terminado.
* Si no ha terminado la funcion termina indicandolo, en caso contrario,
* se continua con la comunicacion sincrona, el envio de resultados y
* se desconectan los grupos de procesos.
*
* Esta funcion permite dos modos de funcionamiento al comprobar si la
* comunicacion asincrona ha terminado.
* Si se utiliza el modo "MAL_USE_NORMAL", se considera terminada cuando
* los padres terminan de enviar.
* Si se utiliza el modo "MAL_USE_IBARRIER", se considera terminada cuando
* los hijos han terminado de recibir.
*/
int check_redistribution(int iter, MPI_Request **comm_req) {
int completed, all_completed, test_err;
MPI_Request *req_completed;
if(config_file->aib == MAL_USE_NORMAL) {
req_completed = &(*comm_req)[0];
} else { // MAL_USE_IBARRIER
req_completed = &(*comm_req)[1];
}
test_err = MPI_Test(req_completed, &completed, MPI_STATUS_IGNORE);
if (test_err != MPI_SUCCESS && test_err != MPI_ERR_PENDING) {
printf("P%d aborting -- Test Async\n", group->myId);
MPI_Abort(MPI_COMM_WORLD, test_err);
}
MPI_Allreduce(&completed, &all_completed, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD);
if(!all_completed) return MAL_ASYNC_PENDING; // Continue only if asynchronous send has ended
MPI_Wait(req_completed, MPI_STATUS_IGNORE);
if(config_file->aib == MAL_USE_IBARRIER) {
MPI_Wait(&(*comm_req)[0], MPI_STATUS_IGNORE); // Indicar como completado el envio asincrono
//Para la desconexión de ambos grupos de procesos es necesario indicar a MPI que esta
//ha terminado, aunque solo se pueda llegar a este punto cuando ha terminado
}
free(*comm_req);
return end_redistribution(iter);
}
/*
* Termina la redistribución de los datos con los hijos, comprobando
* si se han realizado iteraciones con comunicaciones en segundo plano
* y enviando cuantas iteraciones se han realizado a los hijos.
*
* Además se realizan las comunicaciones síncronas se las hay.
* Finalmente termina enviando los datos temporales a los hijos.
*/
int end_redistribution(int iter) {
int rootBcast = MPI_PROC_NULL;
if(group->myId == ROOT) rootBcast = MPI_ROOT;
if(iter > 0) { // Mandar a los hijos iteracion en la que comenzar
MPI_Bcast(&iter, 1, MPI_INT, rootBcast, group->children);
}
if(config_file->sdr > 0) { // Realizar envio sincrono
results->sync_start = MPI_Wtime();
send_sync(group->sync_array, config_file->sdr, group->myId, group->numP, ROOT, group->children, group->numS);
}
send_results(results, rootBcast, config_file->resizes, group->children);
// Desconectar intercomunicador con los hijos
MPI_Comm_disconnect(&(group->children));
return MAL_COMM_COMPLETED;
}
/*
* Inicializacion de los datos de los hijos.
* En la misma se reciben datos de los padres: La configuracion
* de la ejecucion a realizar; y los datos a recibir de los padres
* ya sea de forma sincrona, asincrona o ambas.
*/
void Sons_init() {
// Enviar a los hijos que grupo de procesos son
MPI_Bcast(&(group->grp), 1, MPI_INT, ROOT, group->parents);
MPI_Bcast(&run_id, 1, MPI_INT, ROOT, group->parents);
group->grp++;
config_file = recv_config_file(ROOT, group->parents);
int numP_parents = config_file->procs[group->grp -1];
init_results_data(&results, config_file->resizes - 1, config_file->iters[group->grp]);
if(config_file->adr > 0) { // Recibir datos asincronos
recv_sync(&(group->async_array), config_file->adr, group->myId, group->numP, ROOT, group->parents, numP_parents);
results->async_time[group->grp] = MPI_Wtime();
MPI_Bcast(&(group->iter_start), 1, MPI_INT, ROOT, group->parents);
}
if(config_file->sdr > 0) { // Recibir datos sincronos
recv_sync(&(group->sync_array), config_file->sdr, group->myId, group->numP, ROOT, group->parents, numP_parents);
results->sync_time[group->grp] = MPI_Wtime();
}
// Guardar los resultados de esta transmision
recv_results(results, ROOT, config_file->resizes, group->parents);
if(config_file->sdr > 0) { // Si no hay datos sincronos, el tiempo es 0
results->sync_time[group->grp] = MPI_Wtime() - results->sync_start;
} else {
results->sync_time[group->grp] = 0;
}
if(config_file->adr > 0) { // Si no hay datos asincronos, el tiempo es 0
results->async_time[group->grp] = MPI_Wtime() - results->async_start;
} else {
results->async_time[group->grp] = 0;
}
// Desconectar intercomunicador con los hijos
MPI_Comm_disconnect(&(group->parents));
}
/////////////////////////////////////////
/////////////////////////////////////////
......@@ -395,24 +239,45 @@ void Sons_init() {
* Simula la ejecucción de una iteración de computo en la aplicación
* que dura al menos un tiempo de "time" segundos.
*/
void iterate(double *matrix, int n, int async_comm) {
void iterate(double *matrix, int n, int async_comm, int iter) {
double start_time, actual_time;
double time = config_file->general_time * config_file->factors[group->grp];
double Top = config_file->Top;
int i, operations = 0;
double aux = 0;
start_time = actual_time = MPI_Wtime();
start_time = MPI_Wtime();
operations = time / Top; //FIXME Calcular una sola vez
operations = time / Top;
for(i=0; i < operations; i++) {
aux += computePiSerial(n);
}
/*
if(time >= 1) {
sleep(time);
}
else {
unsigned int sleep_time = time * 1000000;
usleep(sleep_time);
}
*/
if(config_file->comm_tam) {
MPI_Bcast(group->compute_comm_array, config_file->comm_tam, MPI_CHAR, ROOT, comm);
}
actual_time = MPI_Wtime(); // Guardar tiempos
if(async_comm == MAL_ASYNC_PENDING) { // Se esta realizando una redistribucion de datos asincrona
// TODO Que diferencie entre ambas en el IO
if(async_comm == MAL_DIST_PENDING || async_comm == MAL_SPAWN_PENDING || async_comm == MAL_SPAWN_SINGLE_PENDING) { // Se esta realizando una redistribucion de datos asincrona
operations=0;
}
if(results->iter_index == results->iters_size) { // Aumentar tamaño de ambos vectores de resultados
realloc_results_iters(results, results->iters_size + 100);
}
results->iters_time[results->iter_index] = actual_time - start_time;
results->iters_type[results->iter_index] = operations;
results->iter_index = results->iter_index + 1;
......@@ -440,17 +305,18 @@ void print_general_info(int myId, int grp, int numP) {
free(version);
}
/*
* Pide al proceso raiz imprimir los datos sobre las iteraciones realizadas por el grupo de procesos.
*
* Si es el ultimo grupo de procesos, muestra los datos obtenidos de tiempo de ejecucion, creacion de procesos
* y las comunicaciones.
*/
int print_final_results() {
int ptr_local, ptr_global, err;
int print_local_results() {
int ptr_local, ptr_out, err;
char *file_name;
compute_results_iter(results, group->myId, ROOT, comm);
if(group->myId == ROOT) {
ptr_out = dup(1);
file_name = NULL;
file_name = malloc(40 * sizeof(char));
if(file_name == NULL) return -1; // No ha sido posible alojar la memoria
......@@ -459,9 +325,26 @@ int print_final_results() {
create_out_file(file_name, &ptr_local, 1);
print_config_group(config_file, group->grp);
print_iter_results(results, config_file->iters[group->grp] -1);
print_iter_results(*results, config_file->iters[group->grp] - 1);
free(file_name);
fflush(stdout);
close(1);
dup(ptr_out);
}
return 0;
}
/*
* Si es el ultimo grupo de procesos, pide al proceso raiz mostrar los datos obtenidos de tiempo de ejecucion, creacion de procesos
* y las comunicaciones.
*/
int print_final_results() {
int ptr_global, err;
char *file_name;
if(group->myId == ROOT) {
if(group->grp == config_file->resizes -1) {
file_name = NULL;
file_name = malloc(20 * sizeof(char));
......@@ -471,7 +354,8 @@ int print_final_results() {
create_out_file(file_name, &ptr_global, 1);
print_config(config_file, group->grp);
print_global_results(results, config_file->resizes);
print_global_results(*results, config_file->resizes);
fflush(stdout);
free(file_name);
}
......@@ -488,7 +372,6 @@ void init_group_struct(char *argv[], int argc, int myId, int numP) {
group->numP = numP;
group->grp = 0;
group->iter_start = 0;
group->commAsync = MAL_COMM_UNINITIALIZED;
group->argc = argc;
group->argv = argv;
}
......@@ -505,48 +388,67 @@ void init_group_struct(char *argv[], int argc, int myId, int numP) {
void init_application() {
if(group->argc < 2) {
printf("Falta el fichero de configuracion. Uso:\n./programa config.ini id\nEl argumento numerico id es opcional\n");
exit(0);
MPI_Abort(MPI_COMM_WORLD, -1);
}
if(group->argc > 2) {
run_id = atoi(group->argv[2]);
}
config_file = read_ini_file(group->argv[1]);
init_results_data(&results, config_file->resizes, config_file->iters[group->grp]);
if(config_file->sdr > 0) {
results = malloc(sizeof(results_data));
init_results_data(results, config_file->resizes, config_file->iters[group->grp]);
if(config_file->comm_tam) {
group->compute_comm_array = malloc(config_file->comm_tam * sizeof(char));
}
if(config_file->sdr) {
malloc_comm_array(&(group->sync_array), config_file->sdr , group->myId, group->numP);
}
if(config_file->adr > 0) {
if(config_file->adr) {
malloc_comm_array(&(group->async_array), config_file->adr , group->myId, group->numP);
}
obtain_op_times();
}
/*
* Obtiene cuanto tiempo es necesario para realizar una operacion de PI
*/
void obtain_op_times() {
double result, start_time = MPI_Wtime();
int i;
int i, qty = 20000;
result = 0;
for(i=0; i<20000; i++) {
for(i=0; i<qty; i++) {
result += computePiSerial(config_file->matrix_tam);
}
printf("Creado Top con valor %lf\n", result);
fflush(stdout);
//printf("Creado Top con valor %lf\n", result);
//fflush(stdout);
config_file->Top = (MPI_Wtime() - start_time) / 20000; //Tiempo de una iteracion en numero de iteraciones
MPI_Bcast(&(config_file->Top), 1, MPI_DOUBLE, ROOT, MPI_COMM_WORLD);
config_file->Top = (MPI_Wtime() - start_time) / qty; //Tiempo de una operacion
MPI_Bcast(&(config_file->Top), 1, MPI_DOUBLE, ROOT, comm);
}
/*
* Libera toda la memoria asociada con la aplicacion
*/
void free_application_data() {
if(config_file->sdr > 0) {
if(config_file->comm_tam) {
free(group->compute_comm_array);
}
if(config_file->sdr) {
free(group->sync_array);
}
if(config_file->adr > 0) {
if(config_file->adr) {
free(group->async_array);
}
free(group);
free_malleability();
free_config(config_file);
free_results_data(&results);
if(group->grp == 0) { //FIXME Revisar porque cuando es diferente a 0 no funciona
free_results_data(results);
free(results);
}
free(group);
}
......
......@@ -102,15 +102,11 @@ void node_dist(slurm_job_info_t job_record, int type, int total_procs, int **qty
procs[i] += total_procs - asigCores;
(*used_nodes)++;
}
if(*used_nodes > job_record.num_nodes) *used_nodes = job_record.num_nodes;
if(*used_nodes > job_record.num_nodes) *used_nodes = job_record.num_nodes; //FIXME Si ocurre esto no es un error?
}
*used_nodes=job_record.num_nodes;
for(i=0; i<*used_nodes; i++) {
if(procs[i] == 0){
procs[i]++;
}
}
// Antes se ponia aqui todos los nodos sin cpus a 1
*qty = procs;
}
......@@ -153,6 +149,7 @@ void fill_hostfile(slurm_job_info_t job_record, int ptr, int *qty, int used_node
hostlist = slurm_hostlist_create(job_record.nodes);
while ( (host = slurm_hostlist_shift(hostlist)) && i < used_nodes) {
if(qty[i] != 0)
write_hostfile_node(ptr, qty[i], host);
i++;
free(host);
......
module load mpich-3.4.1-noucx
mpicc -Wall Main/Main.c Main/computing_func.c IOcodes/results.c IOcodes/read_ini.c IOcodes/ini.c malleability/ProcessDist.c malleability/CommDist.c -pthread -lslurm -lm
#mpicc -Wall Main/Main.c Main/computing_func.c IOcodes/results.c IOcodes/read_ini.c IOcodes/ini.c malleability/ProcessDist.c malleability/CommDist.c -pthread -lslurm -lm
mpicc -Wall Main/Main.c Main/computing_func.c IOcodes/results.c IOcodes/read_ini.c IOcodes/ini.c malleability/malleabilityManager.c malleability/malleabilityTypes.c malleability/malleabilityZombies.c malleability/ProcessDist.c malleability/CommDist.c -pthread -lslurm -lm
if [ $# -gt 0 ]
then
if [ $1 = "-e" ]
then
cp a.out benchm.out
echo "Creado ejecutable para ejecuciones"
cp a.out bench.out
fi
fi
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <mpi.h>
#include <pthread.h>
#include <math.h>
#include <string.h>
#include <slurm/slurm.h>
#include "ProcessDist.h"
/*
* ESTE CODIGO ES PARA COMPROBAR EL FUNCIONAMIENTO DEL FICHERO ProcessDist.h
* NO TIENE QUE VER CON EL BENCHMARK DE MALEABILIDAD
*/
#define ROOT 0
#define MAXGRP 3
#define TYPE_D 1
// 1 Es nodos
// 2 Es por nucleos
// Función para crear un fichero con el formato GxNPyIDz.o{jobId}.
// El proceso que llama a la función pasa a tener como salida estandar
// dicho fichero.
int create_out_file(int myId, int numP, int grp, char *jobId);
int create_out_file(int myId, int numP, int grp, char *jobId) {
int ptr, err;
char *file_name;
file_name = NULL;
file_name = malloc(40 * sizeof(char));
if(file_name == NULL) return -1; // No ha sido posible alojar la memoria
err = snprintf(file_name, 40, "G%dNP%dID%d.o%s", grp, numP, myId, jobId);
if(err < 0) return -2; // No ha sido posible obtener el nombre de fichero
ptr = open(file_name, O_WRONLY | O_CREAT | O_APPEND, 0644);
if(ptr < 0) return -3; // No ha sido posible crear el fichero
err = close(1);
if(err < 0) return -4; // No es posible modificar la salida estandar
err = dup(ptr);
if(err < 0) return -4; // No es posible modificar la salida estandar
return 0;
}
// Se realizan varios tests de ancho de banda
// al mandar N datos a los procesos impares desde el
// par inmediatamente anterior. Tras esto, los impares
// vuelven a enviar los N datos al proceso par.
//
// Tras las pruebas se imprime el ancho de banda, todo
// el tiempo necesario para realizar todas las pruebas y
// finalmente el tiempo medio por prueba.
void bandwidth(int myId, double latency, int n);
void bandwidth(int myId, double latency, int n) {
int i, loop_count = 100, n_bytes;
double start_time, stop_time, elapsed_time, bw, time;
char *aux;
n_bytes = n * sizeof(char);
aux = malloc(n_bytes);
elapsed_time = 0;
for(i=0; i<loop_count; i++){
MPI_Barrier(MPI_COMM_WORLD);
start_time = MPI_Wtime();
if(myId %2 == 0){
MPI_Ssend(aux, n, MPI_CHAR, myId+1, 99, MPI_COMM_WORLD);
MPI_Recv(aux, n, MPI_CHAR, myId+1, 99, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
}
else if(myId %2 == 1){
MPI_Recv(aux, n, MPI_CHAR, myId-1, 99, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
MPI_Ssend(aux, n, MPI_CHAR, myId-1, 99, MPI_COMM_WORLD);
}
MPI_Barrier(MPI_COMM_WORLD);
stop_time = MPI_Wtime();
elapsed_time += stop_time - start_time;
}
if(myId %2 == 0) {
time = elapsed_time / loop_count - latency;
bw = ((double)n_bytes * 2) / time;
printf("MyId %d Bw=%lf GB/s\nTot time=%lf\nTime=%lf\n", myId, bw/ 1000000000.0, elapsed_time, time);
}
}
// Se realizan varios tests de latencia al
// mandar un único dato de tipo CHAR a los procesos impares
// desde el par inmediatamente anterior. Tras esto, los impares
// vuelven a enviar el dato al proceso par.
//
// Tras las pruebas se imprime el tiempo necesario para realizar
// TODAS las pruebas y se devuleve el tiempo medio (latencia) de
// las pruebas
double ping_pong(int myId, int start);
double ping_pong(int myId, int start) {
int i, loop_count = 100;
double start_time, stop_time, elapsed_time;
char aux;
aux = '0';
elapsed_time = 0;
for(i=0; i<loop_count; i++){
MPI_Barrier(MPI_COMM_WORLD);
start_time = MPI_Wtime();
if(myId % 2 == 0){
MPI_Ssend(&aux, 1, MPI_CHAR, myId+1, 99, MPI_COMM_WORLD);
MPI_Recv(&aux, 1, MPI_CHAR, myId+1, 99, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
}
else if(myId % 2 == 1){
MPI_Recv(&aux, 1, MPI_CHAR, myId-1, 99, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
MPI_Ssend(&aux, 1, MPI_CHAR, myId-1, 99, MPI_COMM_WORLD);
}
MPI_Barrier(MPI_COMM_WORLD);
stop_time = MPI_Wtime();
elapsed_time += stop_time - start_time;
}
if(myId %2 == 0 && start != 0) {
printf("MyId %d Ping=%lf\n", myId, elapsed_time);
elapsed_time/=loop_count;
}
MPI_Bcast(&elapsed_time, 1, MPI_DOUBLE, ROOT, MPI_COMM_WORLD);
return elapsed_time;
}
// Trabajo común para todos los grupos de procesos
int work(int myId, int numP, char **argv, char *job_id) {
int grp, n_value, aux=0;
double latency;
MPI_Comm comm = MPI_COMM_NULL, comm_par= MPI_COMM_NULL;
int rootBcast = MPI_PROC_NULL;
if(myId == ROOT) rootBcast = MPI_ROOT;
// 1.000.000.00 1GB
n_value = 400000000;
grp = 0;
// Obtener que grupo de procesos soy de los padres
MPI_Comm_get_parent(&comm_par);
if(comm_par != MPI_COMM_NULL) {
MPI_Bcast(&grp, 1, MPI_INT, ROOT, comm_par);
grp+=1;
MPI_Barrier(comm_par);
MPI_Bcast(&aux, 1, MPI_INT, rootBcast, comm_par);
//MPI_Comm_free(&comm_par);
MPI_Comm_disconnect(&comm_par);
}
// Dividir los resultados por procesos
//create_out_file(myId, numP, grp, job_id);
/*----- PRUEBAS PRESTACIONES -----*/
// Asegurar que se ha inicializado la comunicación de MPI
ping_pong(myId, 0);
MPI_Barrier(MPI_COMM_WORLD);
// Obtener la latencia de la red
latency = ping_pong(myId, 1);
// Obtener el ancho de banda
bandwidth(myId, latency, n_value);
/*----- CREACIÓN DE PROCESOS -----*/
// Creación de un nuevo grupo de procesos
// Para evitar que se creen más grupos hay que asignar
// el valor 0 en la variable MAXGRP
if(grp != MAXGRP) {
// Inicialización de la comunicación con SLURM
int aux = numP;
init_slurm_comm(argv, myId, aux, ROOT, TYPE_D, COMM_SPAWN_SERIAL);
// Esperar a que la comunicación y creación de procesos
// haya finalizado
int test = -1;
while(test != MPI_SUCCESS) {
test = check_slurm_comm(myId, ROOT, MPI_COMM_WORLD, &comm);
}
// Enviar a los hijos que grupo de procesos son
MPI_Bcast(&grp, 1, MPI_INT, rootBcast, comm);
MPI_Barrier(comm);
MPI_Bcast(&aux, 1, MPI_INT, ROOT, comm);
// Desconectar intercomunicador con los hijos
MPI_Comm_disconnect(&comm);
//MPI_Comm_free(&comm);
} //IF GRP
if(comm != MPI_COMM_NULL || comm_par != MPI_COMM_NULL) {
printf("GRP=%d || El comunicador no esta a NULO\n", grp);
fflush(stdout);
}
return grp;
}
int main(int argc, char ** argv) {
int rank, numP, grp, len, pid;
char *tmp;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &numP);
pid = getpid();
// Imprimir datos sobre el comunicador de
// este grupo de procesos
tmp = getenv("SLURM_JOB_ID");
if(rank == ROOT) {
//system("printenv"); // Imprime todas las variables de entorno
printf("DATA\n");
//print_Info(MPI_COMM_WORLD);
}
// Imprimir nombre del nodo en el que se encuentra el proceso
char *name = malloc(MPI_MAX_PROCESSOR_NAME * sizeof(char));
MPI_Get_processor_name(name,&len);
printf("ID=%d Name %s PID=%d\n", rank, name, pid);
fflush(stdout);
MPI_Barrier(MPI_COMM_WORLD);
// Se manda el trabajo a los hijos
grp = work(rank, numP, argv, tmp);
fflush(stdout);
MPI_Barrier(MPI_COMM_WORLD);
MPI_Finalize();
return 0;
}
......@@ -28,6 +28,9 @@ void recv_sync_arrays(struct Dist_data dist_data, char *array, int root, int num
void send_async_arrays(struct Dist_data dist_data, char *array, int root, int numP_child, int idI, int idE, struct Counts counts, MPI_Request *comm_req);
void recv_async_arrays(struct Dist_data dist_data, char *array, int root, int numP_parents, int idI, int idE, struct Counts counts, MPI_Request *comm_req);
void send_async_point_arrays(struct Dist_data dist_data, char *array, int rootBcast, int numP_child, int idI, int idE, struct Counts counts, MPI_Request *comm_req);
void recv_async_point_arrays(struct Dist_data dist_data, char *array, int root, int numP_parents, int idI, int idE, struct Counts counts, MPI_Request *comm_req);
// DIST FUNCTIONS
void get_dist(int qty, int id, int numP, struct Dist_data *dist_data);
void set_counts(int id, int numP, struct Dist_data data_dist, int *sendcounts);
......@@ -63,7 +66,7 @@ void malloc_comm_array(char **array, int qty, int myId, int numP) {
//================================================================================
//================================================================================
//========================SINCHRONOUS FUNCTIONS===================================
//========================SYNCHRONOUS FUNCTIONS===================================
//================================================================================
//================================================================================
......@@ -136,7 +139,6 @@ void recv_sync(char **array, int qty, int myId, int numP, int root, MPI_Comm int
void send_sync_arrays(struct Dist_data dist_data, char *array, int rootBcast, int numP_child, int idI, int idE, struct Counts counts) {
int i;
// PREPARAR ENVIO DEL VECTOR
if(idI == 0) {
set_counts(0, numP_child, dist_data, counts.counts);
......@@ -147,10 +149,8 @@ void send_sync_arrays(struct Dist_data dist_data, char *array, int rootBcast, in
counts.displs[i] = counts.displs[i-1] + counts.counts[i-1];
}
//print_counts(dist_data, counts.counts, counts.displs, numP_child, "Padres");
/* COMUNICACION DE DATOS */
MPI_Alltoallv(array, counts.counts, counts.displs, MPI_CHAR, NULL, counts.zero_arr, counts.zero_arr, MPI_CHAR, dist_data.intercomm);
}
/*
......@@ -161,7 +161,7 @@ void send_sync_arrays(struct Dist_data dist_data, char *array, int rootBcast, in
void recv_sync_arrays(struct Dist_data dist_data, char *array, int root, int numP_parents, int idI, int idE, struct Counts counts) {
int i;
char *aux = malloc(1);
char aux;
// Ajustar los valores de recepcion
if(idI == 0) {
......@@ -175,14 +175,13 @@ void recv_sync_arrays(struct Dist_data dist_data, char *array, int root, int num
//print_counts(dist_data, counts.counts, counts.displs, numP_parents, "Hijos");
/* COMUNICACION DE DATOS */
MPI_Alltoallv(aux, counts.zero_arr, counts.zero_arr, MPI_CHAR, array, counts.counts, counts.displs, MPI_CHAR, dist_data.intercomm);
free(aux);
MPI_Alltoallv(&aux, counts.zero_arr, counts.zero_arr, MPI_CHAR, array, counts.counts, counts.displs, MPI_CHAR, dist_data.intercomm);
}
//================================================================================
//================================================================================
//========================ASINCHRONOUS FUNCTIONS==================================
//========================ASYNCHRONOUS FUNCTIONS==================================
//================================================================================
//================================================================================
......@@ -196,7 +195,7 @@ void recv_sync_arrays(struct Dist_data dist_data, char *array, int root, int num
* El vector array no se modifica en esta funcion.
*/
int send_async(char *array, int qty, int myId, int numP, int root, MPI_Comm intercomm, int numP_child, MPI_Request **comm_req, int parents_wait) {
int rootBcast = MPI_PROC_NULL;
int i, rootBcast = MPI_PROC_NULL;
int *idS = NULL;
struct Counts counts;
struct Dist_data dist_data;
......@@ -211,17 +210,25 @@ int send_async(char *array, int qty, int myId, int numP, int root, MPI_Comm inte
getIds_intercomm(dist_data, numP_child, &idS); // Obtener rango de Id hijos a los que este proceso manda datos
// MAL_USE_THREAD sigue el camino sincrono
if(parents_wait == MAL_USE_NORMAL) {
*comm_req = (MPI_Request *) malloc(sizeof(MPI_Request));
//*comm_req = (MPI_Request *) malloc(sizeof(MPI_Request));
*comm_req[0] = MPI_REQUEST_NULL;
send_async_arrays(dist_data, array, rootBcast, numP_child, idS[0], idS[1], counts, &(*comm_req[0]));
} else {
*comm_req = (MPI_Request *) malloc(2 * sizeof(MPI_Request));
(*comm_req)[0] = MPI_REQUEST_NULL;
(*comm_req)[1] = MPI_REQUEST_NULL;
} else if (parents_wait == MAL_USE_IBARRIER){
//*comm_req = (MPI_Request *) malloc(2 * sizeof(MPI_Request));
*comm_req[0] = MPI_REQUEST_NULL;
*comm_req[1] = MPI_REQUEST_NULL;
send_async_arrays(dist_data, array, rootBcast, numP_child, idS[0], idS[1], counts, &((*comm_req)[1]));
MPI_Ibarrier(intercomm, &((*comm_req)[0]) );
} else if (parents_wait == MAL_USE_POINT){
//*comm_req = (MPI_Request *) malloc(numP_child * sizeof(MPI_Request));
for(i=0; i<numP_child; i++){
(*comm_req)[i] = MPI_REQUEST_NULL;
}
send_async_point_arrays(dist_data, array, rootBcast, numP_child, idS[0], idS[1], counts, *comm_req);
} else if (parents_wait == MAL_USE_THREAD) { //TODO
}
freeCounts(&counts);
......@@ -242,15 +249,14 @@ int send_async(char *array, int qty, int myId, int numP, int root, MPI_Comm inte
*/
void recv_async(char **array, int qty, int myId, int numP, int root, MPI_Comm intercomm, int numP_parents, int parents_wait) {
int *idS = NULL;
int wait_err;
int wait_err, i;
struct Counts counts;
struct Dist_data dist_data;
MPI_Request comm_req, aux;
MPI_Request *comm_req, aux;
// Obtener distribución para este hijo
get_dist(qty, myId, numP, &dist_data);
*array = malloc(dist_data.tamBl * sizeof(char));
//(*array)[dist_data.tamBl] = '\0';
dist_data.intercomm = intercomm;
/* PREPARAR DATOS DE RECEPCION SOBRE VECTOR*/
......@@ -258,14 +264,28 @@ void recv_async(char **array, int qty, int myId, int numP, int root, MPI_Comm in
getIds_intercomm(dist_data, numP_parents, &idS); // Obtener el rango de Ids de padres del que este proceso recibira datos
recv_async_arrays(dist_data, *array, root, numP_parents, idS[0], idS[1], counts, &comm_req);
// MAL_USE_THREAD sigue el camino sincrono
if(parents_wait == MAL_USE_POINT) {
comm_req = (MPI_Request *) malloc(numP_parents * sizeof(MPI_Request));
for(i=0; i<numP_parents; i++){
comm_req[i] = MPI_REQUEST_NULL;
}
recv_async_point_arrays(dist_data, *array, root, numP_parents, idS[0], idS[1], counts, comm_req);
wait_err = MPI_Waitall(numP_parents, comm_req, MPI_STATUSES_IGNORE);
} else if (parents_wait == MAL_USE_NORMAL || parents_wait == MAL_USE_IBARRIER) {
comm_req = (MPI_Request *) malloc(sizeof(MPI_Request));
*comm_req = MPI_REQUEST_NULL;
recv_async_arrays(dist_data, *array, root, numP_parents, idS[0], idS[1], counts, comm_req);
wait_err = MPI_Wait(comm_req, MPI_STATUS_IGNORE);
} else if (parents_wait == MAL_USE_THREAD) { //TODO
}
wait_err = MPI_Wait(&comm_req, MPI_STATUS_IGNORE);
if(wait_err != MPI_SUCCESS) {
MPI_Abort(MPI_COMM_WORLD, wait_err);
}
if(parents_wait == MAL_USE_IBARRIER) {
if(parents_wait == MAL_USE_IBARRIER) { //MAL USE IBARRIER END
MPI_Ibarrier(intercomm, &aux);
MPI_Wait(&aux, MPI_STATUS_IGNORE); //Es necesario comprobar que la comunicación ha terminado para desconectar los grupos de procesos
}
......@@ -273,12 +293,15 @@ void recv_async(char **array, int qty, int myId, int numP, int root, MPI_Comm in
//printf("S%d Tam %d String: %s END\n", myId, dist_data.tamBl, *array);
freeCounts(&counts);
free(idS);
free(comm_req);
}
/*
* Envia a los hijos un vector que es redistribuido a los procesos
* hijos. Antes de realizar la comunicacion, cada proceso padre calcula sobre que procesos
* del otro grupo se transmiten elementos.
*
* El envio se realiza a partir de una comunicación colectiva.
*/
void send_async_arrays(struct Dist_data dist_data, char *array, int rootBcast, int numP_child, int idI, int idE, struct Counts counts, MPI_Request *comm_req) {
int i;
......@@ -298,10 +321,36 @@ void send_async_arrays(struct Dist_data dist_data, char *array, int rootBcast, i
MPI_Ialltoallv(array, counts.counts, counts.displs, MPI_CHAR, NULL, counts.zero_arr, counts.zero_arr, MPI_CHAR, dist_data.intercomm, comm_req);
}
/*
* Envia a los hijos un vector que es redistribuido a los procesos
* hijos. Antes de realizar la comunicacion, cada proceso padre calcula sobre que procesos
* del otro grupo se transmiten elementos.
*
* El envio se realiza a partir de varias comunicaciones punto a punto.
*/
void send_async_point_arrays(struct Dist_data dist_data, char *array, int rootBcast, int numP_child, int idI, int idE, struct Counts counts, MPI_Request *comm_req) {
int i;
// PREPARAR ENVIO DEL VECTOR
if(idI == 0) {
set_counts(0, numP_child, dist_data, counts.counts);
idI++;
MPI_Isend(array, counts.counts[0], MPI_CHAR, 0, 99, dist_data.intercomm, &(comm_req[0]));
}
for(i=idI; i<idE; i++) {
set_counts(i, numP_child, dist_data, counts.counts);
counts.displs[i] = counts.displs[i-1] + counts.counts[i-1];
MPI_Isend(array+counts.displs[i], counts.counts[i], MPI_CHAR, i, 99, dist_data.intercomm, &(comm_req[i]));
}
//print_counts(dist_data, counts.counts, counts.displs, numP_child, "Padres");
}
/*
* Recibe de los padres un vector que es redistribuido a los procesos
* de este grupo. Antes de realizar la comunicacion cada hijo calcula sobre que procesos
* del otro grupo se transmiten elementos.
*
* La recepcion se realiza a partir de una comunicacion colectiva.
*/
void recv_async_arrays(struct Dist_data dist_data, char *array, int root, int numP_parents, int idI, int idE, struct Counts counts, MPI_Request *comm_req) {
int i;
......@@ -323,6 +372,30 @@ void recv_async_arrays(struct Dist_data dist_data, char *array, int root, int nu
free(aux);
}
/*
* Recibe de los padres un vector que es redistribuido a los procesos
* de este grupo. Antes de realizar la comunicacion cada hijo calcula sobre que procesos
* del otro grupo se transmiten elementos.
*
* La recepcion se realiza a partir de varias comunicaciones punto a punto.
*/
void recv_async_point_arrays(struct Dist_data dist_data, char *array, int root, int numP_parents, int idI, int idE, struct Counts counts, MPI_Request *comm_req) {
int i;
// Ajustar los valores de recepcion
if(idI == 0) {
set_counts(0, numP_parents, dist_data, counts.counts);
idI++;
MPI_Irecv(array, counts.counts[0], MPI_CHAR, 0, 99, dist_data.intercomm, &(comm_req[0])); //FIXME BUffer recv
}
for(i=idI; i<idE; i++) {
set_counts(i, numP_parents, dist_data, counts.counts);
counts.displs[i] = counts.displs[i-1] + counts.counts[i-1];
MPI_Irecv(array+counts.displs[i], counts.counts[i], MPI_CHAR, i, 99, dist_data.intercomm, &(comm_req[i])); //FIXME BUffer recv
}
//print_counts(dist_data, counts.counts, counts.displs, numP_parents, "Hijos");
}
/*
* ========================================================================================
* ========================================================================================
......@@ -477,8 +550,8 @@ void print_counts(struct Dist_data data_dist, int *xcounts, int *xdispls, int si
int i;
for(i=0; i < size; i++) {
if(xcounts[i] != 0) {
//if(xcounts[i] != 0) {
printf("P%d of %d | %scounts[%d]=%d disp=%d\n", data_dist.myId, data_dist.numP, name, i, xcounts[i], xdispls[i]);
}
//}
}
}
......@@ -2,13 +2,16 @@
#include <stdlib.h>
#include <mpi.h>
#include <string.h>
#include "malleabilityStates.h"
#define MAL_COMM_COMPLETED 0
#define MAL_COMM_UNINITIALIZED 2
#define MAL_ASYNC_PENDING 1
//#define MAL_COMM_COMPLETED 0
//#define MAL_COMM_UNINITIALIZED 2
//#define MAL_ASYNC_PENDING 1
#define MAL_USE_NORMAL 0
#define MAL_USE_IBARRIER 1
//#define MAL_USE_NORMAL 0
//#define MAL_USE_IBARRIER 1
//#define MAL_USE_POINT 2
//#define MAL_USE_THREAD 3
int send_sync(char *array, int qty, int myId, int numP, int root, MPI_Comm intercomm, int numP_child);
void recv_sync(char **array, int qty, int myId, int numP, int root, MPI_Comm intercomm, int numP_parents);
......
......@@ -8,43 +8,53 @@
#include <slurm/slurm.h>
#include "ProcessDist.h"
#define ROOT 0
int commSlurm = COMM_UNRESERVED;
int commState = MAL_NOT_STARTED;
struct Slurm_data *slurm_data;
pthread_t slurm_thread;
pthread_t spawn_thread;
pthread_mutex_t spawn_mutex;
MPI_Comm *returned_comm;
double end_time; //FIXME REFACTOR
struct Slurm_data {
char *cmd; // Executable name
int qty_procs;
char *nodelist;
int num_cpus, num_nodes;
int qty_procs, result_procs;
MPI_Info info;
int type_creation;
int spawn_is_single;
};
struct Creation_data {
char **argv;
int numP_childs, type_dist;
};
typedef struct {
char *argv;
int numP_childs, myId, root, already_created;
int type_dist;
int spawn_is_single;
int spawn_method;
MPI_Comm comm;
}Creation_data;
//--------------PRIVATE SPAWN TYPE DECLARATIONS---------------//
void* thread_work(void* creation_data_arg);
//--------------PRIVATE DECLARATIONS---------------//
void processes_dist(char *argv, int numP_childs, int already_created, int type_dist);
void processes_dist(char *argv[], int numP_childs, int type_dist);
void generic_spawn(int myId, int root, int is_single, MPI_Comm *child, MPI_Comm comm);
void single_spawn_connection(int myId, int root, MPI_Comm comm, MPI_Comm *child);
int create_processes(int myId, int root, MPI_Comm *child, MPI_Comm comm);
void node_dist(slurm_job_info_t job_record, int type, int total_procs, int **qty, int *used_nodes);
int create_hostfile(char *jobId, char **file_name);
int write_hostfile_node(int ptr, int qty, char *node_name);
void fill_hostfile(slurm_job_info_t job_record, int ptr, int *qty, int used_nodes);
void node_dist(int type, int total_procs, int already_created, int **qty, int *used_nodes);
//TESTS
void fill_str_hostfile(slurm_job_info_t job_record, int *qty, int used_nodes, char **hostfile_str);
void fill_str_hostfile(int *qty, int used_nodes, char **hostfile_str);
int write_str_node(char **hostfile_str, int len_og, int qty, char *node_name);
//
void print_Info(MPI_Info info);
//@deprecated functions
int create_hostfile(char *jobId, char **file_name);
int write_hostfile_node(int ptr, int qty, char *node_name);
void fill_hostfile(slurm_job_info_t job_record, int ptr, int *qty, int used_nodes);
//--------------PUBLIC FUNCTIONS---------------//
......@@ -60,64 +70,203 @@ void print_Info(MPI_Info info);
*
* Si se pide en segundo plano, llamar a "check_slurm_comm()" comprobara si la configuracion para
* crearlos esta lista, y si es asi, los crea.
*
* Devuelve el estado de el procedimiento. Si no devuelve "COMM_FINISHED", es necesario llamar a
* "check_slurm_comm()".
*/
int init_slurm_comm(char **argv, int myId, int numP, int root, int type_dist, int type_creation) {
int init_slurm_comm(char *argv, int num_cpus, int num_nodes, char *nodelist, int myId, int numP, int numC, int root, int type_dist, int type_creation, int spawn_is_single, MPI_Comm comm, MPI_Comm *child) {
int spawn_qty, already_created = 0;
slurm_data = malloc(sizeof(struct Slurm_data));
if(myId == root) {
spawn_thread = pthread_self();
slurm_data->type_creation = type_creation;
if(type_creation == COMM_SPAWN_SERIAL) {
slurm_data->spawn_is_single = spawn_is_single;
slurm_data->result_procs = numC;
slurm_data->num_cpus = num_cpus;
slurm_data->num_nodes = num_nodes;
slurm_data->nodelist = nodelist;
spawn_qty = numC;
if(type_creation == COMM_SPAWN_MERGE || type_creation == COMM_SPAWN_MERGE_PTHREAD) {
if (numP < slurm_data->result_procs) {
spawn_qty = slurm_data->result_procs - numP;
already_created = numP;
}
}
pthread_mutex_init(&spawn_mutex,NULL);
processes_dist(argv, numP, type_dist);
commSlurm = COMM_FINISHED;
if(type_creation == COMM_SPAWN_SERIAL || slurm_data->type_creation == COMM_SPAWN_MERGE) {
} else if(type_creation == COMM_SPAWN_PTHREAD) {
commSlurm = COMM_IN_PROGRESS;
if(myId == root) {
processes_dist(argv, spawn_qty, already_created, type_dist);
} else {
slurm_data->cmd = malloc(1 * sizeof(char));
slurm_data->info = MPI_INFO_NULL;
}
// WORK
generic_spawn(myId, root, slurm_data->spawn_is_single, child, comm);
// END WORK
if(myId == root && slurm_data->info != MPI_INFO_NULL) {
MPI_Info_free(&(slurm_data->info));
}
pthread_mutex_destroy(&spawn_mutex);
free(slurm_data->cmd);
free(slurm_data);
} else if(type_creation == COMM_SPAWN_PTHREAD || slurm_data->type_creation == COMM_SPAWN_MERGE_PTHREAD) {
commState = MAL_SPAWN_PENDING;
struct Creation_data *creation_data = malloc(sizeof(struct Creation_Data*));
if((spawn_is_single && myId == root) || !spawn_is_single || (slurm_data->type_creation == COMM_SPAWN_MERGE_PTHREAD && numP > slurm_data->result_procs)) {
Creation_data *creation_data = (Creation_data *) malloc(sizeof(Creation_data));
creation_data->argv = argv;
creation_data->numP_childs = numP;
creation_data->numP_childs = spawn_qty;
creation_data->already_created = already_created;
creation_data->myId = myId;
creation_data->root = root;
creation_data->type_dist = type_dist;
creation_data->comm = comm;
if(pthread_create(&slurm_thread, NULL, thread_work, creation_data)) {
if(pthread_create(&spawn_thread, NULL, thread_work, (void *)creation_data)) {
printf("Error al crear el hilo de contacto con SLURM\n");
MPI_Abort(MPI_COMM_WORLD, -1);
return -1;
}
}
}
return 0;
return commState;
}
/*
* Comprueba si una configuracion para crear un nuevo grupo de procesos esta lista,
* y en caso de que lo este, se crea un nuevo grupo de procesos con esa configuracion.
* y en caso de que lo este, se devuelve el communicador a estos nuevos procesos.
*/
int check_slurm_comm(int myId, int root, MPI_Comm comm, MPI_Comm *child) {
int spawn_err = COMM_IN_PROGRESS;
int check_slurm_comm(int myId, int root, int numP, MPI_Comm *child, MPI_Comm comm, MPI_Comm comm_thread, double *real_time) {
if(slurm_data->type_creation == COMM_SPAWN_PTHREAD || slurm_data->type_creation == COMM_SPAWN_MERGE_PTHREAD) {
if (slurm_data->type_creation == COMM_SPAWN_MERGE_PTHREAD && numP > slurm_data->result_procs) { //TODO REFACTOR
printf("Error Check spawn: Configuracion invalida\nSe intenta usar el método Spawn junto a un Shrink merge\n");
MPI_Abort(MPI_COMM_WORLD, -1);
return -10;
}
if(!slurm_data->spawn_is_single || commState == MAL_SPAWN_SINGLE_PENDING || commState == MAL_SPAWN_COMPLETED) {
int state=-10;
//printf("[%d][3] Test min\n", myId); fflush(stdout);
//pthread_mutex_lock(&spawn_mutex); // TODO Descomentar
MPI_Allreduce(&commState, &state, 1, MPI_INT, MPI_MIN, comm);
//pthread_mutex_unlock(&spawn_mutex);
if(myId == root && commSlurm == COMM_FINISHED && slurm_data->type_creation == COMM_SPAWN_PTHREAD) {
if(pthread_join(slurm_thread, NULL)) {
if(state != MAL_SPAWN_COMPLETED) return state; // Continue only if asynchronous process creation has ended
//printf("[%d][5] Test Passed-----------\n", myId); fflush(stdout);
if(pthread_join(spawn_thread, NULL)) {
printf("Error al esperar al hilo\n");
MPI_Abort(MPI_COMM_WORLD, -1);
return -2;
return -10;
}
*child = *returned_comm;
} else if (slurm_data->spawn_is_single) {
//pthread_mutex_lock(&spawn_mutex); // TODO Descomentar
MPI_Bcast(&commState, 1, MPI_INT, root, comm);
//pthread_mutex_unlock(&spawn_mutex);
int threads_not_spawned = pthread_equal(pthread_self(), spawn_thread);
// Non-root processes join root to finalize the spawn
// They also must join if the application has ended its work
if(commState == MAL_SPAWN_SINGLE_START) {
commState = MAL_SPAWN_SINGLE_PENDING;
if(myId != root && threads_not_spawned) {
Creation_data *creation_data = (Creation_data *) malloc(sizeof(Creation_data));
creation_data->argv = NULL;
creation_data->numP_childs = -1;
creation_data->already_created = -1;
creation_data->myId = myId;
creation_data->root = root;
creation_data->type_dist = -1;
creation_data->comm = comm_thread;
if(pthread_create(&spawn_thread, NULL, thread_work, (void *)creation_data)) {
printf("Error al crear el hilo de apoyo\n");
MPI_Abort(MPI_COMM_WORLD, -1);
return -1;
}
}
}
MPI_Bcast(&commSlurm, 1, MPI_INT, root, comm);
// Continue only if asynchronous process creation has ended or application does not have more work
if(commState != MAL_SPAWN_COMPLETED) return commState;
if(commSlurm == COMM_FINISHED) {
spawn_err = create_processes(myId, root, child, comm);
//printf("[%d][4] Test Passed-----------\n", myId); fflush(stdout);
//Asegurar que los hilos han terminado
if(pthread_join(spawn_thread, NULL)) {
printf("Error al esperar al hilo\n");
MPI_Abort(MPI_COMM_WORLD, -1);
return -10;
}
*child = *returned_comm;
} else {
printf("Error Check spawn: Configuracion invalida\n");
MPI_Abort(MPI_COMM_WORLD, -1);
return -10;
}
} else {
return commState;
}
//Free memory
if(myId == root && slurm_data->info != MPI_INFO_NULL) {
MPI_Info_free(&(slurm_data->info));
}
free(slurm_data->cmd);
free(slurm_data);
pthread_mutex_destroy(&spawn_mutex);
spawn_thread = pthread_self();
*real_time=end_time;
return commState;
}
/*
* Conectar grupo de hijos con grupo de padres
* Devuelve un intercomunicador para hablar con los padres
*
* Solo se utiliza cuando la creación de los procesos ha sido
* realizada por un solo proceso padre
*/
void malleability_establish_connection(int myId, int root, MPI_Comm *intercomm) {
char *port_name;
MPI_Comm newintercomm;
if(myId == root) {
port_name = (char *) malloc(MPI_MAX_PORT_NAME * sizeof(char));
MPI_Open_port(MPI_INFO_NULL, port_name);
MPI_Send(port_name, MPI_MAX_PORT_NAME, MPI_CHAR, root, 130, *intercomm);
} else {
port_name = malloc(1);
}
return spawn_err;
MPI_Comm_accept(port_name, MPI_INFO_NULL, root, MPI_COMM_WORLD, &newintercomm);
if(myId == root) {
MPI_Close_port(port_name);
}
free(port_name);
MPI_Comm_free(intercomm);
*intercomm = newintercomm;
}
//--------------PRIVATE SPAWN TYPE FUNCTIONS---------------//
//--------------PRIVATE THREAD FUNCTIONS---------------//
/*
* Funcion llamada por un hilo para que este se encarge
......@@ -127,10 +276,16 @@ int check_slurm_comm(int myId, int root, MPI_Comm comm, MPI_Comm *child) {
* se avisa al hilo maestro.
*/
void* thread_work(void* creation_data_arg) {
struct Creation_data *creation_data = (struct Creation_data*) creation_data_arg;
processes_dist(creation_data->argv, creation_data->numP_childs, creation_data->type_dist);
commSlurm = COMM_FINISHED;
Creation_data *creation_data = (Creation_data*) creation_data_arg;
returned_comm = (MPI_Comm *) malloc(sizeof(MPI_Comm));
if(creation_data->myId == creation_data->root) {
processes_dist(creation_data->argv, creation_data->numP_childs, creation_data->already_created, creation_data->type_dist);
} else {
slurm_data->cmd = malloc(1 * sizeof(char));
slurm_data->info = MPI_INFO_NULL;
}
generic_spawn(creation_data->myId, creation_data->root, slurm_data->spawn_is_single, returned_comm, creation_data->comm);
free(creation_data);
pthread_exit(NULL);
......@@ -138,37 +293,150 @@ void* thread_work(void* creation_data_arg) {
//--------------PRIVATE SPAWN CREATION FUNCTIONS---------------//
/*
* Funcion generica para la creacion de procesos. Obtiene la configuracion
* y segun esta, elige como deberian crearse los procesos.
*
* Cuando termina, modifica la variable global para indicar este cambio
*/
void generic_spawn(int myId, int root, int spawn_is_single, MPI_Comm *child, MPI_Comm comm) {
if(spawn_is_single) {
single_spawn_connection(myId, root, comm, child);
} else {
int rootBcast = MPI_PROC_NULL;
if(myId == root) rootBcast = MPI_ROOT;
create_processes(myId, root, child, comm);
MPI_Bcast(&spawn_is_single, 1, MPI_INT, rootBcast, *child);
}
pthread_mutex_lock(&spawn_mutex);
commState = MAL_SPAWN_COMPLETED;
end_time = MPI_Wtime();
pthread_mutex_unlock(&spawn_mutex);
}
/*
* Crea un grupo de procesos segun la configuracion indicada por la funcion
* "processes_dist()".
*/
int create_processes(int myId, int root, MPI_Comm *child, MPI_Comm comm) {
int spawn_err = MPI_Comm_spawn(slurm_data->cmd, MPI_ARGV_NULL, slurm_data->qty_procs, slurm_data->info, root, comm, child, MPI_ERRCODES_IGNORE);
if(spawn_err != MPI_SUCCESS) {
printf("Error creating new set of %d procs.\n", slurm_data->qty_procs);
}
return spawn_err;
}
/*
* Si la variable "type" es 1, la creación es con la participación de todo el grupo de padres
* Si el valor es diferente, la creación es solo con la participación del proceso root
*/
void single_spawn_connection(int myId, int root, MPI_Comm comm, MPI_Comm *child){
char *port_name;
int auxiliar_conf = COMM_SPAWN_SINGLE;
MPI_Comm newintercomm;
if (myId == root) {
create_processes(myId, root, child, MPI_COMM_SELF);
MPI_Bcast(&auxiliar_conf, 1, MPI_INT, MPI_ROOT, *child);
port_name = (char *) malloc(MPI_MAX_PORT_NAME * sizeof(char));
MPI_Recv(port_name, MPI_MAX_PORT_NAME, MPI_CHAR, root, 130, *child, MPI_STATUS_IGNORE);
commState = MAL_SPAWN_SINGLE_START; // Indicate other processes to join root to end spawn procedure
} else {
port_name = malloc(1);
}
MPI_Comm_connect(port_name, MPI_INFO_NULL, root, comm, &newintercomm);
if(myId == root)
MPI_Comm_free(child);
free(port_name);
*child = newintercomm;
}
//--------------PRIVATE MERGE TYPE FUNCTIONS---------------//
/*
* Se encarga de que el grupo de procesos resultante se
* encuentren todos en un intra comunicador, uniendo a
* padres e hijos en un solo comunicador.
*
* Se llama antes de la redistribución de datos.
*
* TODO REFACTOR
*/
void proc_adapt_expand(int *numP, int numC, MPI_Comm intercomm, MPI_Comm *comm, int is_children_group) {
MPI_Comm new_comm = MPI_COMM_NULL;
MPI_Intercomm_merge(intercomm, is_children_group, &new_comm); //El que pone 0 va primero
//MPI_Comm_free(intercomm); TODO Nueva redistribucion para estos casos y liberar aqui
// *intercomm = MPI_COMM_NULL;
*numP = numC;
if(*comm != MPI_COMM_WORLD && *comm != MPI_COMM_NULL) {
MPI_Comm_free(comm);
}
*comm=new_comm;
}
/*
* Se encarga de que el grupo de procesos resultante se
* eliminen aquellos procesos que ya no son necesarios.
* Los procesos eliminados se quedaran como zombies.
*
* Se llama una vez ha terminado la redistribución de datos.
*/
void proc_adapt_shrink(int numC, MPI_Comm *comm, int myId) {
int color = MPI_UNDEFINED;
MPI_Comm new_comm = MPI_COMM_NULL;
if(myId < numC) {
color = 1;
}
MPI_Comm_split(*comm, color, myId, &new_comm);
if(*comm != MPI_COMM_WORLD && *comm != MPI_COMM_NULL)
//MPI_Comm_free(comm); FIXME
*comm=new_comm;
}
/*
* Configura la creacion de un nuevo grupo de procesos, reservando la memoria
* para una llamada a MPI_Comm_spawn, obteniendo una distribucion fisica
* para los procesos y creando un fichero hostfile.
*/
void processes_dist(char *argv[], int numP_childs, int type) {
int jobId, ptr;
char *tmp;
job_info_msg_t *j_info;
slurm_job_info_t last_record;
void processes_dist(char *argv, int numP_childs, int already_created, int type) {
//int jobId;
//char *tmp;
//job_info_msg_t *j_info;
//slurm_job_info_t last_record;
int used_nodes=0;
int *procs_array;
char *hostfile;
// Get Slurm job info
tmp = getenv("SLURM_JOB_ID");
jobId = atoi(tmp);
slurm_load_job(&j_info, jobId, 1);
last_record = j_info->job_array[j_info->record_count - 1];
//tmp = getenv("SLURM_JOB_ID");
//jobId = atoi(tmp);
//slurm_load_job(&j_info, jobId, 1);
//last_record = j_info->job_array[j_info->record_count - 1];
//COPY PROGRAM NAME
slurm_data->cmd = malloc(strlen(argv[0]) * sizeof(char));
strcpy(slurm_data->cmd, argv[0]);
slurm_data->cmd = malloc(strlen(argv) * sizeof(char));
strcpy(slurm_data->cmd, argv);
// GET NEW DISTRIBUTION
node_dist(last_record, type, numP_childs, &procs_array, &used_nodes);
node_dist(type, numP_childs, already_created, &procs_array, &used_nodes);
slurm_data->qty_procs = numP_childs;
/*
// CREATE/UPDATE HOSTFILE
int ptr;
ptr = create_hostfile(tmp, &hostfile);
MPI_Info_create(&(slurm_data->info));
MPI_Info_set(slurm_data->info, "hostfile", hostfile);
......@@ -177,36 +445,18 @@ void processes_dist(char *argv[], int numP_childs, int type) {
// SET NEW DISTRIBUTION
fill_hostfile(last_record, ptr, procs_array, used_nodes);
close(ptr);
*/
// TEST
/*
fill_str_hostfile(last_record, procs_array, used_nodes, &hostfile);
// CREATE AND SET STRING HOSTFILE
fill_str_hostfile(procs_array, used_nodes, &hostfile);
MPI_Info_create(&(slurm_data->info));
MPI_Info_set(slurm_data->info, "hosts", hostfile);
*/
// Free JOB INFO
slurm_free_job_info_msg(j_info);
}
/*
* Crea un grupo de procesos segun la configuracion indicada por la funcion
* "processes_dist()".
*/
int create_processes(int myId, int root, MPI_Comm *child, MPI_Comm comm) {
int spawn_err = MPI_Comm_spawn(slurm_data->cmd, MPI_ARGV_NULL, slurm_data->qty_procs, slurm_data->info, root, comm, child, MPI_ERRCODES_IGNORE);
if(spawn_err != MPI_SUCCESS) {
printf("Error creating new set of %d procs.\n", slurm_data->qty_procs);
}
free(hostfile);
free(procs_array);
if(myId == root) {
MPI_Info_free(&(slurm_data->info));
free(slurm_data->cmd);
}
return spawn_err;
// Free JOB INFO
//slurm_free_job_info_msg(j_info);
}
/*
......@@ -221,41 +471,51 @@ int create_processes(int myId, int root, MPI_Comm *child, MPI_Comm comm) {
* COMM_PHY_CPU (2): Orientada a completar la capacidad de un nodo antes de
* ocupar otro nodo.
*/
void node_dist(slurm_job_info_t job_record, int type, int total_procs, int **qty, int *used_nodes) {
void node_dist(int type, int total_procs, int already_created, int **qty, int *used_nodes) {
int i, asigCores;
int tamBl, remainder;
int *procs;
procs = calloc(job_record.num_nodes, sizeof(int)); // Numero de procesos por nodo
procs = calloc(slurm_data->num_nodes, sizeof(int)); // Numero de procesos por nodo
/* GET NEW DISTRIBUTION */
if(type == 1) { // DIST NODES
*used_nodes = job_record.num_nodes;
tamBl = total_procs / job_record.num_nodes;
remainder = total_procs % job_record.num_nodes;
*used_nodes = slurm_data->num_nodes;
tamBl = total_procs / slurm_data->num_nodes;
remainder = total_procs % slurm_data->num_nodes;
for(i=0; i<remainder; i++) {
procs[i] = tamBl + 1;
}
for(i=remainder; i<job_record.num_nodes; i++) {
for(i=remainder; i<slurm_data->num_nodes; i++) {
procs[i] = tamBl;
}
} else if (type == 2) { // DIST CPUs
tamBl = job_record.num_cpus / job_record.num_nodes;
tamBl = slurm_data->num_cpus / slurm_data->num_nodes;
asigCores = 0;
i = 0;
*used_nodes = 0;
i = *used_nodes = already_created / tamBl;
remainder = already_created % tamBl;
//First node could already have existing procs
if (remainder) {
procs[i] = asigCores = tamBl - remainder;
i = (i+1) % slurm_data->num_nodes;
(*used_nodes)++;
}
//Assing tamBl to each node
while(asigCores+tamBl <= total_procs) {
asigCores += tamBl;
procs[i] += tamBl;
i = (i+1) % job_record.num_nodes;
i = (i+1) % slurm_data->num_nodes;
(*used_nodes)++;
}
//Last node could have less procs than tamBl
if(asigCores < total_procs) {
procs[i] += total_procs - asigCores;
(*used_nodes)++;
}
if(*used_nodes > job_record.num_nodes) *used_nodes = job_record.num_nodes;
if(*used_nodes > slurm_data->num_nodes) *used_nodes = slurm_data->num_nodes; //FIXME Si ocurre esto no es un error?
}
*qty = calloc(*used_nodes, sizeof(int)); // Numero de procesos por nodo
......@@ -263,9 +523,75 @@ void node_dist(slurm_job_info_t job_record, int type, int total_procs, int **qty
(*qty)[i] = procs[i];
}
free(procs);
}
/*
* Crea y devuelve una cadena para ser utilizada por la llave "hosts"
* al crear procesos e indicar donde tienen que ser creados.
*/
void fill_str_hostfile(int *qty, int used_nodes, char **hostfile_str) {
int i=0, len=0;
char *host;
hostlist_t hostlist;
hostlist = slurm_hostlist_create(slurm_data->nodelist);
while ( (host = slurm_hostlist_shift(hostlist)) && i < used_nodes) {
if(qty[i] != 0) {
len = write_str_node(hostfile_str, len, qty[i], host);
}
i++;
free(host);
}
slurm_hostlist_destroy(hostlist);
}
/*
* Añade en una cadena "qty" entradas de "node_name".
* Realiza la reserva de memoria y la realoja si es necesario.
*/
int write_str_node(char **hostfile_str, int len_og, int qty, char *node_name) {
int err, len_node, len, i;
char *ocurrence;
len_node = strlen(node_name);
len = qty * (len_node + 1);
if(len_og == 0) { // Memoria no reservada
*hostfile_str = (char *) malloc(len * sizeof(char) - (1 * sizeof(char)));
} else { // Cadena ya tiene datos
*hostfile_str = (char *) realloc(*hostfile_str, (len_og + len) * sizeof(char) - (1 * sizeof(char)));
}
if(hostfile_str == NULL) return -1; // No ha sido posible alojar la memoria
ocurrence = (char *) malloc((len_node+1) * sizeof(char));
if(ocurrence == NULL) return -1; // No ha sido posible alojar la memoria
err = sprintf(ocurrence, ",%s", node_name);
if(err < 0) return -2; // No ha sido posible escribir sobre la variable auxiliar
i=0;
if(len_og == 0) { // Si se inicializa, la primera es una copia
i++;
strcpy(*hostfile_str, node_name);
}
for(; i<qty; i++){ // Las siguientes se conctanenan
strcat(*hostfile_str, ocurrence);
}
free(ocurrence);
return len+len_og;
}
//====================================================
//====================================================
//============DEPRECATED FUNCTIONS====================
//====================================================
//====================================================
/*
* @deprecated
* Crea un fichero que se utilizara como hostfile
* para un nuevo grupo de procesos.
*
......@@ -293,6 +619,7 @@ int create_hostfile(char *jobId, char **file_name) {
}
/*
* @deprecated
* Rellena un fichero hostfile indicado por ptr con los nombres
* de los nodos a utilizar indicados por "job_record" y la cantidad
* de procesos que alojara cada nodo indicado por "qty".
......@@ -312,6 +639,7 @@ void fill_hostfile(slurm_job_info_t job_record, int ptr, int *qty, int used_node
}
/*
* @deprecated
* Escribe en el fichero hostfile indicado por ptr una nueva linea.
*
* Esta linea indica el nombre de un nodo y la cantidad de procesos a
......@@ -336,51 +664,3 @@ int write_hostfile_node(int ptr, int qty, char *node_name) {
return 0;
}
void fill_str_hostfile(slurm_job_info_t job_record, int *qty, int used_nodes, char **hostfile_str) {
int i=0, len=0;
char *host;
hostlist_t hostlist;
hostlist = slurm_hostlist_create(job_record.nodes);
while ( (host = slurm_hostlist_shift(hostlist)) && i < used_nodes) {
len = write_str_node(hostfile_str, len, qty[i], host);
i++;
free(host);
}
slurm_hostlist_destroy(hostlist);
}
int write_str_node(char **hostfile_str, int len_og, int qty, char *node_name) {
int err, len_node, len, i;
char *ocurrence;
len_node = strlen(node_name);
len = qty * (len_node + 1);
if(len_og == 0) { // Memoria no reservada
*hostfile_str = (char *) malloc(len * sizeof(char));
} else { // Cadena ya tiene datos
*hostfile_str = (char *) realloc(*hostfile_str, (len_og + len) * sizeof(char));
}
if(hostfile_str == NULL) return -1; // No ha sido posible alojar la memoria
ocurrence = (char *) malloc((len_node+1) * sizeof(char));
if(ocurrence == NULL) return -1; // No ha sido posible alojar la memoria
err = sprintf(ocurrence, "%s,", node_name);
if(err < 0) return -2; // No ha sido posible escribir sobre la variable auxiliar
i=0;
if(len_og == 0) {
i++;
strcpy(*hostfile_str, ocurrence);
}
for(; i<qty; i++){
strcat(*hostfile_str, ocurrence);
}
free(ocurrence);
return len;
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment